├── README.md ├── .travis.yml ├── test_data ├── subdir │ └── .gitkeep ├── dot_git │ ├── HEAD │ ├── ORIG_HEAD │ ├── refs │ │ ├── remotes │ │ │ └── origin │ │ │ │ ├── HEAD │ │ │ │ └── master │ │ ├── tags │ │ │ └── 0.1 │ │ └── heads │ │ │ └── master │ ├── index │ ├── description │ ├── packed-refs │ ├── objects │ │ ├── 14 │ │ │ └── 269af1a4371bdacef25554946c5b20583c0faf │ │ ├── 15 │ │ │ └── d1ad58065cac69e7d79b77897ca6c5b7127873 │ │ ├── 28 │ │ │ └── aa8b36458441cb4fb2f02060c2bd665940de08 │ │ ├── 37 │ │ │ └── 213e7bb3c334a0f7708c7afcab5babb3f95434 │ │ ├── 39 │ │ │ └── ff220ca5c9c04c03949294f4eb87f73c73ad33 │ │ ├── 49 │ │ │ └── bac2b0a923fe6481c7cc207837cf663748c1ed │ │ ├── 69 │ │ │ └── b1e37ac37fd8f2ee479a1fdcae38029e1d5677 │ │ ├── 85 │ │ │ └── fd1ab5a369352f1b11feec2c0efcbf140cc721 │ │ ├── 93 │ │ │ └── 0d8889d5c5578b06e9f298b662957d7c587fec │ │ ├── 98 │ │ │ ├── 70e974ea29d642201fcd529d2c388ef2a79f67 │ │ │ └── b4e2b830cd7d21c32645003000bab43797d336 │ │ ├── 0b │ │ │ └── fba4d397e196cde4606e4b12fa6836b79ba289 │ │ ├── 0c │ │ │ └── 8257b5f5348dc6cfd29e5e519d058535d5678f │ │ ├── 0d │ │ │ └── 85b7725d99bb423bd82b876833058b35f5eff2 │ │ ├── 1a │ │ │ └── 973cbe990376838fdb3c92e2bcfb07ffd05fb6 │ │ ├── 1e │ │ │ └── fecd717188441397c07f267cf468fdf04d4796 │ │ ├── 2c │ │ │ └── 225b962d6666011c69ca5c2c67204959f8ba32 │ │ ├── 4a │ │ │ └── d1024e3b47a466fedb59d1f8875768cf22e0fc │ │ ├── 4b │ │ │ ├── ab381d0209b95160f8cc8761fe479ad72187d8 │ │ │ └── cf76c2891d6e3833e79255dfd96f655f36b6d0 │ │ ├── 4c │ │ │ └── 19e634ce724b0fe609cc4c5f0473ea587f762c │ │ ├── 6e │ │ │ └── fa5de2e1f1cc4c0c84577513477b8425f9adcd │ │ ├── 6f │ │ │ ├── 576add8d19588490fc98e21c1f8391ce7b567c │ │ │ └── 63668c72544be8efb5bcf9c6a29676e92de64a │ │ ├── 7d │ │ │ └── 6d47bb24a82a9b01552eee4482e06ff2ae9382 │ │ ├── 7e │ │ │ └── f13d8499c0c30637b6acd9815d7f3c3b2725e3 │ │ ├── 8f │ │ │ └── 62acc498a4a57654dbcb66e773b905370aa6fb │ │ ├── 9f │ │ │ └── b81b9fff91d71585e3b94cf8f19a4e58878005 │ │ ├── a2 │ │ │ └── 3ffd186352c167850e29222d1e5244db53422b │ │ ├── af │ │ │ └── 6e4fe91a8f9a0f3c03cbec9e1d2aac47345d67 │ │ ├── b4 │ │ │ └── 99a18d0aea475863ef88dcdb0941ca71a53b13 │ │ ├── b5 │ │ │ └── 29456652ccde17e7ac195278315f238e1b0d65 │ │ ├── b8 │ │ │ └── a944cec759bdc37de3e99db5cb77be8ff38a0d │ │ ├── c7 │ │ │ ├── 4aa3a82c8b028e12994167dce319c523bdf713 │ │ │ └── c6efd4b754392d997da56cb48f8c2fbd2d151f │ │ ├── c9 │ │ │ └── b4a98fd0720609e086293d53a28b1ad8d41c55 │ │ ├── d0 │ │ │ └── 1ad4bb2a2d01532024f8367f975b34129ea1df │ │ ├── d2 │ │ │ └── edc8e3a2be5777a7be101ff6e0d3a15ddcd7a1 │ │ ├── d5 │ │ │ └── 64d0bc3dd917926892c55e3706cc116d5b165e │ │ ├── d8 │ │ │ └── 2b3c84105642e86c0957b033e9fd4404bc6721 │ │ ├── dd │ │ │ └── 2c92cfa7e60967b38b4a05b206ab490924503a │ │ ├── e6 │ │ │ └── 9de29bb2d1d6434b8b29ae775ad8c2e48c5391 │ │ ├── e9 │ │ │ └── 38051ce0798670620163918f30184e6cb2ca8a │ │ ├── eb │ │ │ └── c0dc96e31a00e88cb1bfa4d57f50f85cf7a132 │ │ ├── ed │ │ │ ├── b44ff324c403625e5911f67ae5a48f0b825de2 │ │ │ └── fa0533e0c7b9527d89f218b2cc5f579bb5c913 │ │ ├── ee │ │ │ └── 4a2a5fd44de3a32f3f867a0d2cecf041b7b4c2 │ │ ├── f1 │ │ │ └── 1d3b263e0b2fe742c16713bd5a3200cd4eea14 │ │ ├── f4 │ │ │ └── 5d37d9add8f21eb84678f6d2c66377c4dd0c5e │ │ ├── f6 │ │ │ └── 0f302bb29040c4eab00617aef2d1a2582f0ebb │ │ ├── fc │ │ │ └── cd7a23e400c82657e775cd652030876c10ca90 │ │ └── pack │ │ │ ├── pack-d310969c4ba0ebfe725685fa577a1eec5ecb15b2.idx │ │ │ └── pack-d310969c4ba0ebfe725685fa577a1eec5ecb15b2.pack │ ├── FETCH_HEAD │ ├── logs │ │ ├── refs │ │ │ ├── remotes │ │ │ │ └── origin │ │ │ │ │ ├── HEAD │ │ │ │ │ └── master │ │ │ └── heads │ │ │ │ └── master │ │ └── HEAD │ ├── hooks │ │ ├── post-update.sample │ │ ├── pre-applypatch.sample │ │ ├── applypatch-msg.sample │ │ ├── commit-msg.sample │ │ ├── prepare-commit-msg.sample │ │ ├── pre-push.sample │ │ ├── pre-commit.sample │ │ ├── update.sample │ │ └── pre-rebase.sample │ ├── info │ │ └── exclude │ └── config ├── zlib-delta ├── test-delta-new.delta ├── test-delta.c ├── test-delta-new.c ├── zlib.c ├── zlib-changed.c └── LICENSE ├── .gitignore ├── .appveyor.yml ├── setup_test.go ├── cat-file.go ├── object_test.go ├── packobjecttype_string.go ├── log_test.go ├── gitgo └── gitgo.go ├── README ├── log.go ├── scanner.go ├── delta_test.go ├── repository.go ├── pack.go ├── delta.go ├── verify-pack_test.go ├── cat-file_test.go ├── verify-pack.go ├── LICENSE └── object.go /README.md: -------------------------------------------------------------------------------- 1 | README -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | -------------------------------------------------------------------------------- /test_data/subdir/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test_data/dot_git/HEAD: -------------------------------------------------------------------------------- 1 | ref: refs/heads/master 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.swo 3 | *.swn 4 | gitgo/gitgo 5 | -------------------------------------------------------------------------------- /test_data/dot_git/ORIG_HEAD: -------------------------------------------------------------------------------- 1 | b499a18d0aea475863ef88dcdb0941ca71a53b13 2 | -------------------------------------------------------------------------------- /test_data/dot_git/refs/remotes/origin/HEAD: -------------------------------------------------------------------------------- 1 | ref: refs/remotes/origin/master 2 | -------------------------------------------------------------------------------- /test_data/dot_git/refs/tags/0.1: -------------------------------------------------------------------------------- 1 | 49bac2b0a923fe6481c7cc207837cf663748c1ed 2 | -------------------------------------------------------------------------------- /test_data/dot_git/refs/heads/master: -------------------------------------------------------------------------------- 1 | 37213e7bb3c334a0f7708c7afcab5babb3f95434 2 | -------------------------------------------------------------------------------- /test_data/dot_git/refs/remotes/origin/master: -------------------------------------------------------------------------------- 1 | 37213e7bb3c334a0f7708c7afcab5babb3f95434 2 | -------------------------------------------------------------------------------- /test_data/zlib-delta: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/zlib-delta -------------------------------------------------------------------------------- /.appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | GOPATH: c:\gopath 3 | 4 | build_script: 5 | - go test -v 6 | -------------------------------------------------------------------------------- /test_data/dot_git/index: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/index -------------------------------------------------------------------------------- /test_data/dot_git/description: -------------------------------------------------------------------------------- 1 | Unnamed repository; edit this file 'description' to name the repository. 2 | -------------------------------------------------------------------------------- /test_data/test-delta-new.delta: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/test-delta-new.delta -------------------------------------------------------------------------------- /test_data/dot_git/packed-refs: -------------------------------------------------------------------------------- 1 | # pack-refs with: peeled fully-peeled 2 | fe89ee30bbcdfdf376beae530cc53f967012f31c refs/remotes/origin/master 3 | -------------------------------------------------------------------------------- /test_data/dot_git/objects/0b/fba4d397e196cde4606e4b12fa6836b79ba289: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/0b/fba4d397e196cde4606e4b12fa6836b79ba289 -------------------------------------------------------------------------------- /test_data/dot_git/objects/0c/8257b5f5348dc6cfd29e5e519d058535d5678f: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/0c/8257b5f5348dc6cfd29e5e519d058535d5678f -------------------------------------------------------------------------------- /test_data/dot_git/objects/0d/85b7725d99bb423bd82b876833058b35f5eff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/0d/85b7725d99bb423bd82b876833058b35f5eff2 -------------------------------------------------------------------------------- /test_data/dot_git/objects/14/269af1a4371bdacef25554946c5b20583c0faf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/14/269af1a4371bdacef25554946c5b20583c0faf -------------------------------------------------------------------------------- /test_data/dot_git/objects/15/d1ad58065cac69e7d79b77897ca6c5b7127873: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/15/d1ad58065cac69e7d79b77897ca6c5b7127873 -------------------------------------------------------------------------------- /test_data/dot_git/objects/1a/973cbe990376838fdb3c92e2bcfb07ffd05fb6: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/1a/973cbe990376838fdb3c92e2bcfb07ffd05fb6 -------------------------------------------------------------------------------- /test_data/dot_git/objects/1e/fecd717188441397c07f267cf468fdf04d4796: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/1e/fecd717188441397c07f267cf468fdf04d4796 -------------------------------------------------------------------------------- /test_data/dot_git/objects/28/aa8b36458441cb4fb2f02060c2bd665940de08: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/28/aa8b36458441cb4fb2f02060c2bd665940de08 -------------------------------------------------------------------------------- /test_data/dot_git/objects/2c/225b962d6666011c69ca5c2c67204959f8ba32: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/2c/225b962d6666011c69ca5c2c67204959f8ba32 -------------------------------------------------------------------------------- /test_data/dot_git/objects/37/213e7bb3c334a0f7708c7afcab5babb3f95434: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/37/213e7bb3c334a0f7708c7afcab5babb3f95434 -------------------------------------------------------------------------------- /test_data/dot_git/objects/39/ff220ca5c9c04c03949294f4eb87f73c73ad33: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/39/ff220ca5c9c04c03949294f4eb87f73c73ad33 -------------------------------------------------------------------------------- /test_data/dot_git/objects/49/bac2b0a923fe6481c7cc207837cf663748c1ed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/49/bac2b0a923fe6481c7cc207837cf663748c1ed -------------------------------------------------------------------------------- /test_data/dot_git/objects/4a/d1024e3b47a466fedb59d1f8875768cf22e0fc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/4a/d1024e3b47a466fedb59d1f8875768cf22e0fc -------------------------------------------------------------------------------- /test_data/dot_git/objects/4b/ab381d0209b95160f8cc8761fe479ad72187d8: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/4b/ab381d0209b95160f8cc8761fe479ad72187d8 -------------------------------------------------------------------------------- /test_data/dot_git/objects/4b/cf76c2891d6e3833e79255dfd96f655f36b6d0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/4b/cf76c2891d6e3833e79255dfd96f655f36b6d0 -------------------------------------------------------------------------------- /test_data/dot_git/objects/4c/19e634ce724b0fe609cc4c5f0473ea587f762c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/4c/19e634ce724b0fe609cc4c5f0473ea587f762c -------------------------------------------------------------------------------- /test_data/dot_git/objects/69/b1e37ac37fd8f2ee479a1fdcae38029e1d5677: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/69/b1e37ac37fd8f2ee479a1fdcae38029e1d5677 -------------------------------------------------------------------------------- /test_data/dot_git/objects/6e/fa5de2e1f1cc4c0c84577513477b8425f9adcd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/6e/fa5de2e1f1cc4c0c84577513477b8425f9adcd -------------------------------------------------------------------------------- /test_data/dot_git/objects/6f/576add8d19588490fc98e21c1f8391ce7b567c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/6f/576add8d19588490fc98e21c1f8391ce7b567c -------------------------------------------------------------------------------- /test_data/dot_git/objects/6f/63668c72544be8efb5bcf9c6a29676e92de64a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/6f/63668c72544be8efb5bcf9c6a29676e92de64a -------------------------------------------------------------------------------- /test_data/dot_git/objects/7d/6d47bb24a82a9b01552eee4482e06ff2ae9382: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/7d/6d47bb24a82a9b01552eee4482e06ff2ae9382 -------------------------------------------------------------------------------- /test_data/dot_git/objects/7e/f13d8499c0c30637b6acd9815d7f3c3b2725e3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/7e/f13d8499c0c30637b6acd9815d7f3c3b2725e3 -------------------------------------------------------------------------------- /test_data/dot_git/objects/85/fd1ab5a369352f1b11feec2c0efcbf140cc721: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/85/fd1ab5a369352f1b11feec2c0efcbf140cc721 -------------------------------------------------------------------------------- /test_data/dot_git/objects/8f/62acc498a4a57654dbcb66e773b905370aa6fb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/8f/62acc498a4a57654dbcb66e773b905370aa6fb -------------------------------------------------------------------------------- /test_data/dot_git/objects/93/0d8889d5c5578b06e9f298b662957d7c587fec: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/93/0d8889d5c5578b06e9f298b662957d7c587fec -------------------------------------------------------------------------------- /test_data/dot_git/objects/98/70e974ea29d642201fcd529d2c388ef2a79f67: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/98/70e974ea29d642201fcd529d2c388ef2a79f67 -------------------------------------------------------------------------------- /test_data/dot_git/objects/98/b4e2b830cd7d21c32645003000bab43797d336: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/98/b4e2b830cd7d21c32645003000bab43797d336 -------------------------------------------------------------------------------- /test_data/dot_git/objects/9f/b81b9fff91d71585e3b94cf8f19a4e58878005: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/9f/b81b9fff91d71585e3b94cf8f19a4e58878005 -------------------------------------------------------------------------------- /test_data/dot_git/objects/a2/3ffd186352c167850e29222d1e5244db53422b: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/a2/3ffd186352c167850e29222d1e5244db53422b -------------------------------------------------------------------------------- /test_data/dot_git/objects/af/6e4fe91a8f9a0f3c03cbec9e1d2aac47345d67: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/af/6e4fe91a8f9a0f3c03cbec9e1d2aac47345d67 -------------------------------------------------------------------------------- /test_data/dot_git/objects/b4/99a18d0aea475863ef88dcdb0941ca71a53b13: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/b4/99a18d0aea475863ef88dcdb0941ca71a53b13 -------------------------------------------------------------------------------- /test_data/dot_git/objects/b5/29456652ccde17e7ac195278315f238e1b0d65: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/b5/29456652ccde17e7ac195278315f238e1b0d65 -------------------------------------------------------------------------------- /test_data/dot_git/objects/b8/a944cec759bdc37de3e99db5cb77be8ff38a0d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/b8/a944cec759bdc37de3e99db5cb77be8ff38a0d -------------------------------------------------------------------------------- /test_data/dot_git/objects/c7/4aa3a82c8b028e12994167dce319c523bdf713: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/c7/4aa3a82c8b028e12994167dce319c523bdf713 -------------------------------------------------------------------------------- /test_data/dot_git/objects/c7/c6efd4b754392d997da56cb48f8c2fbd2d151f: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/c7/c6efd4b754392d997da56cb48f8c2fbd2d151f -------------------------------------------------------------------------------- /test_data/dot_git/objects/c9/b4a98fd0720609e086293d53a28b1ad8d41c55: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/c9/b4a98fd0720609e086293d53a28b1ad8d41c55 -------------------------------------------------------------------------------- /test_data/dot_git/objects/d0/1ad4bb2a2d01532024f8367f975b34129ea1df: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/d0/1ad4bb2a2d01532024f8367f975b34129ea1df -------------------------------------------------------------------------------- /test_data/dot_git/objects/d2/edc8e3a2be5777a7be101ff6e0d3a15ddcd7a1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/d2/edc8e3a2be5777a7be101ff6e0d3a15ddcd7a1 -------------------------------------------------------------------------------- /test_data/dot_git/objects/d5/64d0bc3dd917926892c55e3706cc116d5b165e: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/d5/64d0bc3dd917926892c55e3706cc116d5b165e -------------------------------------------------------------------------------- /test_data/dot_git/objects/d8/2b3c84105642e86c0957b033e9fd4404bc6721: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/d8/2b3c84105642e86c0957b033e9fd4404bc6721 -------------------------------------------------------------------------------- /test_data/dot_git/objects/dd/2c92cfa7e60967b38b4a05b206ab490924503a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/dd/2c92cfa7e60967b38b4a05b206ab490924503a -------------------------------------------------------------------------------- /test_data/dot_git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 -------------------------------------------------------------------------------- /test_data/dot_git/objects/e9/38051ce0798670620163918f30184e6cb2ca8a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/e9/38051ce0798670620163918f30184e6cb2ca8a -------------------------------------------------------------------------------- /test_data/dot_git/objects/eb/c0dc96e31a00e88cb1bfa4d57f50f85cf7a132: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/eb/c0dc96e31a00e88cb1bfa4d57f50f85cf7a132 -------------------------------------------------------------------------------- /test_data/dot_git/objects/ed/b44ff324c403625e5911f67ae5a48f0b825de2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/ed/b44ff324c403625e5911f67ae5a48f0b825de2 -------------------------------------------------------------------------------- /test_data/dot_git/objects/ed/fa0533e0c7b9527d89f218b2cc5f579bb5c913: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/ed/fa0533e0c7b9527d89f218b2cc5f579bb5c913 -------------------------------------------------------------------------------- /test_data/dot_git/objects/ee/4a2a5fd44de3a32f3f867a0d2cecf041b7b4c2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/ee/4a2a5fd44de3a32f3f867a0d2cecf041b7b4c2 -------------------------------------------------------------------------------- /test_data/dot_git/objects/f1/1d3b263e0b2fe742c16713bd5a3200cd4eea14: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/f1/1d3b263e0b2fe742c16713bd5a3200cd4eea14 -------------------------------------------------------------------------------- /test_data/dot_git/objects/f4/5d37d9add8f21eb84678f6d2c66377c4dd0c5e: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/f4/5d37d9add8f21eb84678f6d2c66377c4dd0c5e -------------------------------------------------------------------------------- /test_data/dot_git/objects/f6/0f302bb29040c4eab00617aef2d1a2582f0ebb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/f6/0f302bb29040c4eab00617aef2d1a2582f0ebb -------------------------------------------------------------------------------- /test_data/dot_git/objects/fc/cd7a23e400c82657e775cd652030876c10ca90: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/fc/cd7a23e400c82657e775cd652030876c10ca90 -------------------------------------------------------------------------------- /test_data/dot_git/objects/pack/pack-d310969c4ba0ebfe725685fa577a1eec5ecb15b2.idx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/pack/pack-d310969c4ba0ebfe725685fa577a1eec5ecb15b2.idx -------------------------------------------------------------------------------- /test_data/dot_git/objects/pack/pack-d310969c4ba0ebfe725685fa577a1eec5ecb15b2.pack: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChimeraCoder/gitgo/HEAD/test_data/dot_git/objects/pack/pack-d310969c4ba0ebfe725685fa577a1eec5ecb15b2.pack -------------------------------------------------------------------------------- /test_data/dot_git/FETCH_HEAD: -------------------------------------------------------------------------------- 1 | 37213e7bb3c334a0f7708c7afcab5babb3f95434 branch 'master' of github.com:ChimeraCoder/gitgo 2 | 49bac2b0a923fe6481c7cc207837cf663748c1ed not-for-merge tag '0.1' of github.com:ChimeraCoder/gitgo 3 | -------------------------------------------------------------------------------- /test_data/dot_git/logs/refs/remotes/origin/HEAD: -------------------------------------------------------------------------------- 1 | 0000000000000000000000000000000000000000 fe89ee30bbcdfdf376beae530cc53f967012f31c Ahmed Abdalla 1428355983 -0400 clone: from git@github.com:ChimeraCoder/gitgo.git 2 | -------------------------------------------------------------------------------- /test_data/dot_git/hooks/post-update.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to prepare a packed repository for use over 4 | # dumb transports. 5 | # 6 | # To enable this hook, rename this file to "post-update". 7 | 8 | exec git update-server-info 9 | -------------------------------------------------------------------------------- /test_data/dot_git/info/exclude: -------------------------------------------------------------------------------- 1 | # git ls-files --others --exclude-from=.git/info/exclude 2 | # Lines that start with '#' are comments. 3 | # For a project mostly in C, the following would be a good set of 4 | # exclude patterns (uncomment them if you want to use them): 5 | # *.[oa] 6 | # *~ 7 | -------------------------------------------------------------------------------- /test_data/dot_git/config: -------------------------------------------------------------------------------- 1 | [core] 2 | repositoryformatversion = 0 3 | filemode = true 4 | bare = false 5 | logallrefupdates = true 6 | ignorecase = true 7 | precomposeunicode = true 8 | [remote "origin"] 9 | url = git@github.com:ChimeraCoder/gitgo.git 10 | fetch = +refs/heads/*:refs/remotes/origin/* 11 | [branch "master"] 12 | remote = origin 13 | merge = refs/heads/master 14 | -------------------------------------------------------------------------------- /test_data/dot_git/hooks/pre-applypatch.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to verify what is about to be committed 4 | # by applypatch from an e-mail message. 5 | # 6 | # The hook should exit with non-zero status after issuing an 7 | # appropriate message if it wants to stop the commit. 8 | # 9 | # To enable this hook, rename this file to "pre-applypatch". 10 | 11 | . git-sh-setup 12 | test -x "$GIT_DIR/hooks/pre-commit" && 13 | exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"} 14 | : 15 | -------------------------------------------------------------------------------- /test_data/dot_git/logs/refs/remotes/origin/master: -------------------------------------------------------------------------------- 1 | fe89ee30bbcdfdf376beae530cc53f967012f31c 6f63668c72544be8efb5bcf9c6a29676e92de64a Ahmed Abdalla 1428610355 -0400 pull -u: fast-forward 2 | 6f63668c72544be8efb5bcf9c6a29676e92de64a b499a18d0aea475863ef88dcdb0941ca71a53b13 Ahmed Abdalla 1428637809 -0400 pull -u: fast-forward 3 | b499a18d0aea475863ef88dcdb0941ca71a53b13 37213e7bb3c334a0f7708c7afcab5babb3f95434 Ahmed Abdalla 1428951031 -0400 pull -u: fast-forward 4 | -------------------------------------------------------------------------------- /test_data/dot_git/hooks/applypatch-msg.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to check the commit log message taken by 4 | # applypatch from an e-mail message. 5 | # 6 | # The hook should exit with non-zero status after issuing an 7 | # appropriate message if it wants to stop the commit. The hook is 8 | # allowed to edit the commit message file. 9 | # 10 | # To enable this hook, rename this file to "applypatch-msg". 11 | 12 | . git-sh-setup 13 | test -x "$GIT_DIR/hooks/commit-msg" && 14 | exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"} 15 | : 16 | -------------------------------------------------------------------------------- /setup_test.go: -------------------------------------------------------------------------------- 1 | package gitgo 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "path" 7 | ) 8 | 9 | var RepoDir *os.File 10 | 11 | func init() { 12 | 13 | _, err := os.Stat(path.Join("test_data", ".git")) 14 | if err != nil { 15 | if !os.IsNotExist(err) { 16 | log.Fatal(err) 17 | } 18 | err := os.Symlink(path.Join("dot_git"), path.Join("test_data", ".git")) 19 | if err != nil { 20 | log.Fatal(err) 21 | } 22 | } 23 | } 24 | 25 | func init() { 26 | var err error 27 | RepoDir, err = os.Open(path.Join("test_data", ".git")) 28 | if err != nil { 29 | panic(err) 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /test_data/dot_git/logs/refs/heads/master: -------------------------------------------------------------------------------- 1 | 0000000000000000000000000000000000000000 fe89ee30bbcdfdf376beae530cc53f967012f31c Ahmed Abdalla 1428355983 -0400 clone: from git@github.com:ChimeraCoder/gitgo.git 2 | fe89ee30bbcdfdf376beae530cc53f967012f31c 6f63668c72544be8efb5bcf9c6a29676e92de64a Ahmed Abdalla 1428610355 -0400 pull -u: Fast-forward 3 | 6f63668c72544be8efb5bcf9c6a29676e92de64a b499a18d0aea475863ef88dcdb0941ca71a53b13 Ahmed Abdalla 1428637809 -0400 pull -u: Fast-forward 4 | b499a18d0aea475863ef88dcdb0941ca71a53b13 37213e7bb3c334a0f7708c7afcab5babb3f95434 Ahmed Abdalla 1428951031 -0400 pull -u: Fast-forward 5 | -------------------------------------------------------------------------------- /cat-file.go: -------------------------------------------------------------------------------- 1 | package gitgo 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "os" 7 | ) 8 | 9 | type keyType string 10 | 11 | // SHA represents the SHA-1 hash used by git 12 | type SHA string 13 | 14 | const ( 15 | treeKey keyType = "tree" 16 | parentKey = "parent" 17 | authorKey = "author" 18 | committerKey = "committer" 19 | ) 20 | 21 | // CatFile implements git cat-file for the command-line 22 | // tool. Currently it supports only the -t fiag 23 | func CatFile(name SHA) (io.Reader, error) { 24 | pwd, err := os.Open(".") 25 | if err != nil { 26 | return nil, err 27 | } 28 | obj, err := NewObject(name, *pwd) 29 | if err != nil { 30 | return nil, err 31 | } 32 | return bytes.NewReader([]byte(obj.Type())), err 33 | } 34 | -------------------------------------------------------------------------------- /object_test.go: -------------------------------------------------------------------------------- 1 | package gitgo 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func Test_parseAuthorString(t *testing.T) { 9 | const input = "aditya 1428349755 -0400" 10 | const expectedAuthor = "aditya " 11 | loc, err := time.LoadLocation("America/New_York") 12 | if err != nil { 13 | t.Fatal(err) 14 | } 15 | 16 | expectedDate := time.Unix(1428349755, 0) 17 | expectedDate = expectedDate.In(loc) 18 | author, date, err := parseAuthorString(input) 19 | if err != nil { 20 | t.Fatal(err) 21 | } 22 | if author != expectedAuthor { 23 | t.Errorf("expected author %s and received %s", expectedAuthor, author) 24 | } 25 | if !expectedDate.Equal(date) { 26 | t.Errorf("expected date %s and received %s", expectedDate, date) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packobjecttype_string.go: -------------------------------------------------------------------------------- 1 | // generated by stringer -type=packObjectType; DO NOT EDIT 2 | 3 | package gitgo 4 | 5 | import "fmt" 6 | 7 | const ( 8 | _packObjectType_name_0 = "OBJ_COMMITOBJ_TREEOBJ_BLOBOBJ_TAG" 9 | _packObjectType_name_1 = "OBJ_OFS_DELTAOBJ_REF_DELTA" 10 | ) 11 | 12 | var ( 13 | _packObjectType_index_0 = [...]uint8{0, 10, 18, 26, 33} 14 | _packObjectType_index_1 = [...]uint8{0, 13, 26} 15 | ) 16 | 17 | func (i packObjectType) String() string { 18 | switch { 19 | case 1 <= i && i <= 4: 20 | i -= 1 21 | return _packObjectType_name_0[_packObjectType_index_0[i]:_packObjectType_index_0[i+1]] 22 | case 6 <= i && i <= 7: 23 | i -= 6 24 | return _packObjectType_name_1[_packObjectType_index_1[i]:_packObjectType_index_1[i+1]] 25 | default: 26 | return fmt.Sprintf("packObjectType(%d)", i) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test_data/dot_git/hooks/commit-msg.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to check the commit log message. 4 | # Called by "git commit" with one argument, the name of the file 5 | # that has the commit message. The hook should exit with non-zero 6 | # status after issuing an appropriate message if it wants to stop the 7 | # commit. The hook is allowed to edit the commit message file. 8 | # 9 | # To enable this hook, rename this file to "commit-msg". 10 | 11 | # Uncomment the below to add a Signed-off-by line to the message. 12 | # Doing this in a hook is a bad idea in general, but the prepare-commit-msg 13 | # hook is more suited to it. 14 | # 15 | # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') 16 | # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" 17 | 18 | # This example catches duplicate Signed-off-by lines. 19 | 20 | test "" = "$(grep '^Signed-off-by: ' "$1" | 21 | sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { 22 | echo >&2 Duplicate Signed-off-by lines. 23 | exit 1 24 | } 25 | -------------------------------------------------------------------------------- /test_data/dot_git/logs/HEAD: -------------------------------------------------------------------------------- 1 | 0000000000000000000000000000000000000000 fe89ee30bbcdfdf376beae530cc53f967012f31c Ahmed Abdalla 1428355983 -0400 clone: from git@github.com:ChimeraCoder/gitgo.git 2 | fe89ee30bbcdfdf376beae530cc53f967012f31c 6f63668c72544be8efb5bcf9c6a29676e92de64a Ahmed Abdalla 1428610355 -0400 pull -u: Fast-forward 3 | 6f63668c72544be8efb5bcf9c6a29676e92de64a 97eed02ebe122df8fdd853c1215d8775f3d9f1a1 Ahmed Abdalla 1428610838 -0400 checkout: moving from master to 97eed02ebe122df8fdd853c1215d8775f3d9f1a1 4 | 97eed02ebe122df8fdd853c1215d8775f3d9f1a1 6f63668c72544be8efb5bcf9c6a29676e92de64a Ahmed Abdalla 1428610861 -0400 checkout: moving from 97eed02ebe122df8fdd853c1215d8775f3d9f1a1 to master 5 | 6f63668c72544be8efb5bcf9c6a29676e92de64a b499a18d0aea475863ef88dcdb0941ca71a53b13 Ahmed Abdalla 1428637809 -0400 pull -u: Fast-forward 6 | b499a18d0aea475863ef88dcdb0941ca71a53b13 37213e7bb3c334a0f7708c7afcab5babb3f95434 Ahmed Abdalla 1428951031 -0400 pull -u: Fast-forward 7 | -------------------------------------------------------------------------------- /log_test.go: -------------------------------------------------------------------------------- 1 | package gitgo 2 | 3 | import ( 4 | "log" 5 | "testing" 6 | ) 7 | 8 | func Test_Log(t *testing.T) { 9 | const input SHA = "1d833eb5b6c5369c0cb7a4a3e20ded237490145f" 10 | expected := []SHA{"1d833eb5b6c5369c0cb7a4a3e20ded237490145f", "a7f92c920ce85f07a33f948aa4fa2548b270024f", "97eed02ebe122df8fdd853c1215d8775f3d9f1a1"} 11 | parents, err := Log(input, RepoDir) 12 | if err != nil { 13 | t.Error(err) 14 | } 15 | 16 | if len(parents) != len(expected) { 17 | t.Errorf("received %d parents (expected %d)", len(parents), len(expected)) 18 | for _, parent := range parents { 19 | log.Printf("%+v\n", parent) 20 | } 21 | return 22 | } 23 | for i, parent := range parents { 24 | if parent.Name != expected[i] { 25 | t.Errorf("received incorrect parents: \nexpected: %+v\nreceived: %+v", expected, parents) 26 | } 27 | } 28 | } 29 | 30 | // This function reads from the current repository, rather than the 31 | // test data repository 32 | // If it fails, we want a warning, but still have a zero exit status 33 | // due to the system-specific nature of the test 34 | func Test_SlowLog(t *testing.T) { 35 | const input SHA = "a3dda0b50b190caf79ea5074ed6490f30ea47cef" 36 | _, err := Log(input, nil) 37 | if err != nil { 38 | t.Skip("Failed to read %s: %s", input, err) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /gitgo/gitgo.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "log" 8 | "os" 9 | 10 | "github.com/ChimeraCoder/gitgo" 11 | ) 12 | 13 | func main() { 14 | args := os.Args 15 | if len(args) <= 2 { 16 | fmt.Println("gitgo: must provide either cat-file or log subcommand") 17 | return 18 | } 19 | module := args[1] 20 | switch module { 21 | case "cat-file": 22 | if len(args) < 3 { 23 | fmt.Println("must specify hash with `cat-file`") 24 | os.Exit(1) 25 | } 26 | hash := args[2] 27 | result, err := gitgo.CatFile(gitgo.SHA(hash)) 28 | if err != nil { 29 | log.Fatal(err) 30 | } 31 | io.Copy(os.Stdout, result) 32 | 33 | case "log": 34 | if len(args) < 3 { 35 | fmt.Println("must specify commit name with `log`") 36 | os.Exit(1) 37 | } 38 | hash := gitgo.SHA(args[2]) 39 | commits, err := gitgo.Log(hash, nil) 40 | if err != nil { 41 | log.Fatal(err) 42 | } 43 | b := bytes.NewBuffer(nil) 44 | for _, commit := range commits { 45 | fmt.Fprintf(b, "commit %s\nAuthor: %s\nDate: %s\n\n %s\n", commit.Name, commit.Author, commit.AuthorDate.Format(gitgo.RFC2822), bytes.Replace(commit.Message, []byte("\n"), []byte("\n "), -1)) 46 | } 47 | io.Copy(os.Stdout, b) 48 | default: 49 | log.Fatalf("no such command: %s", module) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /test_data/dot_git/hooks/prepare-commit-msg.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to prepare the commit log message. 4 | # Called by "git commit" with the name of the file that has the 5 | # commit message, followed by the description of the commit 6 | # message's source. The hook's purpose is to edit the commit 7 | # message file. If the hook fails with a non-zero status, 8 | # the commit is aborted. 9 | # 10 | # To enable this hook, rename this file to "prepare-commit-msg". 11 | 12 | # This hook includes three examples. The first comments out the 13 | # "Conflicts:" part of a merge commit. 14 | # 15 | # The second includes the output of "git diff --name-status -r" 16 | # into the message, just before the "git status" output. It is 17 | # commented because it doesn't cope with --amend or with squashed 18 | # commits. 19 | # 20 | # The third example adds a Signed-off-by line to the message, that can 21 | # still be edited. This is rarely a good idea. 22 | 23 | case "$2,$3" in 24 | merge,) 25 | /usr/bin/perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;; 26 | 27 | # ,|template,) 28 | # /usr/bin/perl -i.bak -pe ' 29 | # print "\n" . `git diff --cached --name-status -r` 30 | # if /^#/ && $first++ == 0' "$1" ;; 31 | 32 | *) ;; 33 | esac 34 | 35 | # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') 36 | # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" 37 | -------------------------------------------------------------------------------- /test_data/dot_git/hooks/pre-push.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # An example hook script to verify what is about to be pushed. Called by "git 4 | # push" after it has checked the remote status, but before anything has been 5 | # pushed. If this script exits with a non-zero status nothing will be pushed. 6 | # 7 | # This hook is called with the following parameters: 8 | # 9 | # $1 -- Name of the remote to which the push is being done 10 | # $2 -- URL to which the push is being done 11 | # 12 | # If pushing without using a named remote those arguments will be equal. 13 | # 14 | # Information about the commits which are being pushed is supplied as lines to 15 | # the standard input in the form: 16 | # 17 | # 18 | # 19 | # This sample shows how to prevent push of commits where the log message starts 20 | # with "WIP" (work in progress). 21 | 22 | remote="$1" 23 | url="$2" 24 | 25 | z40=0000000000000000000000000000000000000000 26 | 27 | while read local_ref local_sha remote_ref remote_sha 28 | do 29 | if [ "$local_sha" = $z40 ] 30 | then 31 | # Handle delete 32 | : 33 | else 34 | if [ "$remote_sha" = $z40 ] 35 | then 36 | # New branch, examine all commits 37 | range="$local_sha" 38 | else 39 | # Update to existing branch, examine new commits 40 | range="$remote_sha..$local_sha" 41 | fi 42 | 43 | # Check for WIP commit 44 | commit=`git rev-list -n 1 --grep '^WIP' "$range"` 45 | if [ -n "$commit" ] 46 | then 47 | echo >&2 "Found WIP commit in $local_ref, not pushing" 48 | exit 1 49 | fi 50 | fi 51 | done 52 | 53 | exit 0 54 | -------------------------------------------------------------------------------- /test_data/dot_git/hooks/pre-commit.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to verify what is about to be committed. 4 | # Called by "git commit" with no arguments. The hook should 5 | # exit with non-zero status after issuing an appropriate message if 6 | # it wants to stop the commit. 7 | # 8 | # To enable this hook, rename this file to "pre-commit". 9 | 10 | if git rev-parse --verify HEAD >/dev/null 2>&1 11 | then 12 | against=HEAD 13 | else 14 | # Initial commit: diff against an empty tree object 15 | against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 16 | fi 17 | 18 | # If you want to allow non-ASCII filenames set this variable to true. 19 | allownonascii=$(git config --bool hooks.allownonascii) 20 | 21 | # Redirect output to stderr. 22 | exec 1>&2 23 | 24 | # Cross platform projects tend to avoid non-ASCII filenames; prevent 25 | # them from being added to the repository. We exploit the fact that the 26 | # printable range starts at the space character and ends with tilde. 27 | if [ "$allownonascii" != "true" ] && 28 | # Note that the use of brackets around a tr range is ok here, (it's 29 | # even required, for portability to Solaris 10's /usr/bin/tr), since 30 | # the square bracket bytes happen to fall in the designated range. 31 | test $(git diff --cached --name-only --diff-filter=A -z $against | 32 | LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 33 | then 34 | cat <<\EOF 35 | Error: Attempt to add a non-ASCII file name. 36 | 37 | This can cause problems if you want to work with people on other platforms. 38 | 39 | To be portable it is advisable to rename the file. 40 | 41 | If you know what you are doing you can disable this check using: 42 | 43 | git config hooks.allownonascii true 44 | EOF 45 | exit 1 46 | fi 47 | 48 | # If there are whitespace errors, print the offending file names and fail. 49 | exec git diff-index --check --cached $against -- 50 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | gitgo 2 | ======== 3 | [![GoDoc](https://godoc.org/github.com/ChimeraCoder/gitgo?status.svg)](https://godoc.org/github.com/ChimeraCoder/gitgo) 4 | [![Build Status](https://travis-ci.org/ChimeraCoder/gitgo.svg?branch=master)](https://travis-ci.org/ChimeraCoder/gitgo) 5 | 6 | 7 | Gitgo provides Go functions for interacting with Git repositories. 8 | 9 | Unlike libgit2, which is written in C, Gitgo is written in pure Go, and can be compiled and cross-compiled easily for all platforms supported by Go. 10 | 11 | 12 | Installation 13 | -------------- 14 | 15 | ` 16 | $ go get github.com/ChimeraCoder/gitgo 17 | ` 18 | 19 | 20 | Usage 21 | --------- 22 | 23 | Full documentation is available on [GoDoc](https://godoc.org/github.com/ChimeraCoder/gitgo). 24 | 25 | Gitgo is a library intended to be used by other applications, rather than a replacement for the `git` command-line tools. However, gitgo does provide the `gitgo` binary, which is used for testing the gitgo library functions and demonstrating their functionality: 26 | 27 | ```` 28 | $ cd $GOPATH/src/github.com/ChimeraCoder/gitgo 29 | $ gitgo log 1d833eb5b6c5369c0cb7a4a3e20ded237490145f 30 | commit 1d833eb5b6c5369c0cb7a4a3e20ded237490145f 31 | Author: aditya 32 | Date: Mon Apr 6 15:49:15 2015 -0400 33 | 34 | Parse git object into struct and add corresponding test 35 | 36 | commit a7f92c920ce85f07a33f948aa4fa2548b270024f 37 | Author: aditya 38 | Date: Fri Apr 3 12:38:24 2015 -0400 39 | 40 | Add CatFile function and corresponding test 41 | 42 | commit 97eed02ebe122df8fdd853c1215d8775f3d9f1a1 43 | Author: aditya 44 | Date: Fri Apr 3 11:45:00 2015 -0400 45 | 46 | First commit. Create .gitignore 47 | 48 | ```` 49 | 50 | Note that the `gitgo` binary does not support all (or even most) of the functions provided by the gitgo library; it is intended for demonstration purposes and testing only. 51 | -------------------------------------------------------------------------------- /log.go: -------------------------------------------------------------------------------- 1 | package gitgo 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "reflect" 7 | ) 8 | 9 | // Log is equivalent to `git log `. If basedir is non-nil 10 | // and points to a valid git respository, the command will be run 11 | // using that repository. 12 | func Log(name SHA, basedir *os.File) ([]Commit, error) { 13 | dir, err := findGitDir(basedir) 14 | if err != nil { 15 | return nil, err 16 | } 17 | defer dir.Close() 18 | 19 | repo := Repository{Basedir: *dir} 20 | as, err := repo.allAncestors(name) 21 | if err != nil { 22 | return nil, err 23 | } 24 | 25 | obj, err := repo.Object(name) 26 | if err != nil { 27 | return nil, fmt.Errorf("commit not found: %s", err) 28 | } 29 | result := make([]Commit, len(as)+1) 30 | result[0] = obj.(Commit) 31 | for i, o := range as { 32 | result[i+1] = o 33 | } 34 | return result, nil 35 | } 36 | 37 | func (r *Repository) allAncestors(name SHA) ([]Commit, error) { 38 | basedir := r.Basedir 39 | obj, err := r.Object(name) 40 | if err != nil { 41 | return nil, fmt.Errorf("commit not found: %s", err) 42 | } 43 | 44 | var commit Commit 45 | switch obj := obj.(type) { 46 | case *packObject: 47 | // TODO check that this case is logically possible 48 | commit, err = obj.Commit(basedir) 49 | if err != nil { 50 | return nil, err 51 | } 52 | case Commit: 53 | commit = obj 54 | default: 55 | return nil, fmt.Errorf("not a commit") 56 | } 57 | 58 | parents := []Commit{} 59 | if len(commit.Parents) > 0 { 60 | // By default, git-log uses the first parent in merges 61 | obj, err := r.Object(commit.Parents[0]) 62 | if err != nil { 63 | return nil, err 64 | } 65 | parent, ok := obj.(Commit) 66 | if !ok { 67 | fmt.Println(reflect.TypeOf(obj)) 68 | return nil, fmt.Errorf("receved non-commit object parent: %s (%s)", commit.Parents[0], obj.Type()) 69 | } 70 | 71 | parents = append(parents, parent) 72 | ancestors, err := r.allAncestors(SHA(commit.Parents[0])) 73 | if err != nil { 74 | return parents, err 75 | } 76 | parents = append(parents, ancestors...) 77 | } 78 | return parents, nil 79 | } 80 | -------------------------------------------------------------------------------- /scanner.go: -------------------------------------------------------------------------------- 1 | package gitgo 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | ) 8 | 9 | // scanner functions similarly to bufio.Scanner, 10 | // except that it never reads more input than necessary, 11 | // which allow predictable consumption (and reuse) of readers 12 | type scanner struct { 13 | r io.Reader 14 | data []byte 15 | err error 16 | } 17 | 18 | func (s *scanner) scan() bool { 19 | if s.err != nil { 20 | return false 21 | } 22 | s.data = s.read() 23 | return s.err == nil 24 | } 25 | 26 | func (s *scanner) Err() error { 27 | if s.err == io.EOF { 28 | return nil 29 | } 30 | return s.err 31 | } 32 | 33 | func (s *scanner) read() []byte { 34 | if s.err != nil { 35 | return nil 36 | } 37 | result := make([]byte, 1) 38 | n, err := s.r.Read(result) 39 | if err != nil { 40 | s.err = err 41 | return nil 42 | } 43 | if n == 0 { 44 | s.err = fmt.Errorf("read zero bytes") 45 | } 46 | return result 47 | } 48 | 49 | // ScanNullLines is like bufio.ScanLines, except it uses the null character as the delimiter 50 | // instead of a newline 51 | func ScanNullLines(data []byte, atEOF bool) (advance int, token []byte, err error) { 52 | if atEOF && len(data) == 0 { 53 | return 0, nil, nil 54 | } 55 | if i := bytes.IndexByte(data, '\x00'); i >= 0 { 56 | // We have a full null-terminated line. 57 | return i + 1, data[0:i], nil 58 | } 59 | // If we're at EOF, we have a final, non-terminated "line". Return it. 60 | if atEOF { 61 | return len(data), data, nil 62 | } 63 | // Request more data. 64 | return 0, nil, nil 65 | } 66 | 67 | // ScanLinesNoTrim is exactly like bufio.ScanLines, except it does not trim the newline 68 | func ScanLinesNoTrim(data []byte, atEOF bool) (advance int, token []byte, err error) { 69 | if atEOF && len(data) == 0 { 70 | return 0, nil, nil 71 | } 72 | if i := bytes.IndexByte(data, '\n'); i >= 0 { 73 | // We have a full newline-terminated line. 74 | return i + 1, data[0 : i+1], nil 75 | } 76 | // If we're at EOF, we have a final, non-terminated line. Return it. 77 | if atEOF { 78 | return len(data), data, nil 79 | } 80 | // Request more data. 81 | return 0, nil, nil 82 | } 83 | -------------------------------------------------------------------------------- /test_data/test-delta.c: -------------------------------------------------------------------------------- 1 | /* 2 | * test-delta.c: test code to exercise diff-delta.c and patch-delta.c 3 | * 4 | * (C) 2005 Nicolas Pitre 5 | * 6 | * This code is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License version 2 as 8 | * published by the Free Software Foundation. 9 | */ 10 | 11 | #include "git-compat-util.h" 12 | #include "delta.h" 13 | #include "cache.h" 14 | 15 | static const char usage_str[] = 16 | "test-delta (-d|-p) "; 17 | 18 | int main(int argc, char *argv[]) 19 | { 20 | int fd; 21 | struct stat st; 22 | void *from_buf, *data_buf, *out_buf; 23 | unsigned long from_size, data_size, out_size; 24 | 25 | if (argc != 5 || (strcmp(argv[1], "-d") && strcmp(argv[1], "-p"))) { 26 | fprintf(stderr, "usage: %s\n", usage_str); 27 | return 1; 28 | } 29 | 30 | fd = open(argv[2], O_RDONLY); 31 | if (fd < 0 || fstat(fd, &st)) { 32 | perror(argv[2]); 33 | return 1; 34 | } 35 | from_size = st.st_size; 36 | from_buf = mmap(NULL, from_size, PROT_READ, MAP_PRIVATE, fd, 0); 37 | if (from_buf == MAP_FAILED) { 38 | perror(argv[2]); 39 | close(fd); 40 | return 1; 41 | } 42 | close(fd); 43 | 44 | fd = open(argv[3], O_RDONLY); 45 | if (fd < 0 || fstat(fd, &st)) { 46 | perror(argv[3]); 47 | return 1; 48 | } 49 | data_size = st.st_size; 50 | data_buf = mmap(NULL, data_size, PROT_READ, MAP_PRIVATE, fd, 0); 51 | if (data_buf == MAP_FAILED) { 52 | perror(argv[3]); 53 | close(fd); 54 | return 1; 55 | } 56 | close(fd); 57 | 58 | if (argv[1][1] == 'd') 59 | out_buf = diff_delta(from_buf, from_size, 60 | data_buf, data_size, 61 | &out_size, 0); 62 | else 63 | out_buf = patch_delta(from_buf, from_size, 64 | data_buf, data_size, 65 | &out_size); 66 | if (!out_buf) { 67 | fprintf(stderr, "delta operation failed (returned NULL)\n"); 68 | return 1; 69 | } 70 | 71 | fd = open (argv[4], O_WRONLY|O_CREAT|O_TRUNC, 0666); 72 | if (fd < 0 || write_in_full(fd, out_buf, out_size) != out_size) { 73 | perror(argv[4]); 74 | return 1; 75 | } 76 | 77 | return 0; 78 | } 79 | -------------------------------------------------------------------------------- /delta_test.go: -------------------------------------------------------------------------------- 1 | package gitgo 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "io/ioutil" 7 | "log" 8 | "os" 9 | "reflect" 10 | "testing" 11 | ) 12 | 13 | func Test_PatchDelta(t *testing.T) { 14 | suite := [][3]string{ 15 | [3]string{"test_data/test-delta.c", "test_data/test-delta-new.delta", "test_data/test-delta-new.c"}, 16 | [3]string{"test_data/zlib.c", "test_data/zlib-delta", "test_data/zlib-changed.c"}, 17 | } 18 | 19 | for _, trpl := range suite { 20 | 21 | fin, err := os.Open(trpl[0]) 22 | if err != nil { 23 | t.Error(err) 24 | continue 25 | } 26 | defer fin.Close() 27 | 28 | deltaf, err := os.Open(trpl[1]) 29 | if err != nil { 30 | t.Error(err) 31 | continue 32 | } 33 | defer deltaf.Close() 34 | 35 | expectedf, err := os.Open(trpl[2]) 36 | if err != nil { 37 | t.Error(err) 38 | continue 39 | } 40 | defer expectedf.Close() 41 | 42 | restored, err := patchDelta(fin, deltaf) 43 | if err != nil { 44 | t.Error(err) 45 | continue 46 | } 47 | 48 | bts1, err := ioutil.ReadAll(expectedf) 49 | if err != nil { 50 | t.Error(err) 51 | continue 52 | } 53 | 54 | bts2, err := ioutil.ReadAll(restored) 55 | if err != nil { 56 | t.Error(err) 57 | continue 58 | } 59 | 60 | if len(bts1) != len(bts2) { 61 | t.Errorf("Expected %d bytes and received %d", len(bts1), len(bts2)) 62 | log.Printf("%q", bts2) 63 | continue 64 | } 65 | 66 | if !reflect.DeepEqual(bts1, bts2) { 67 | t.Errorf("delta application failed") 68 | continue 69 | } 70 | } 71 | } 72 | 73 | func Test_parseVarInt(t *testing.T) { 74 | type pair struct { 75 | b []byte 76 | i int 77 | } 78 | inputs := []pair{ 79 | pair{[]byte{145, 46}, 5905}, 80 | pair{[]byte{137, 49}, 6281}, 81 | } 82 | for _, p := range inputs { 83 | input := p.b 84 | expected := p.i 85 | result, err := parseVarInt(bytes.NewBuffer(input)) 86 | if err != nil { 87 | t.Error(err) 88 | return 89 | } 90 | if result != expected { 91 | t.Errorf("Expected %d and received %d", expected, result) 92 | } 93 | } 94 | } 95 | 96 | // readersEqual checks that two readers have the same contents 97 | func readersEqual(r1, r2 io.Reader) bool { 98 | bts, err := ioutil.ReadAll(r1) 99 | if err != nil { 100 | panic(err) 101 | } 102 | bts2, err := ioutil.ReadAll(r2) 103 | if err != nil { 104 | panic(err) 105 | } 106 | log.Printf("1: %s", bts) 107 | log.Printf("2: %s", bts2) 108 | if !reflect.DeepEqual(bts, bts2) { 109 | return false 110 | } 111 | return true 112 | } 113 | -------------------------------------------------------------------------------- /repository.go: -------------------------------------------------------------------------------- 1 | package gitgo 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | ) 8 | 9 | type Repository struct { 10 | Basedir os.File 11 | packfiles []*packfile 12 | } 13 | 14 | func (r *Repository) Object(input SHA) (obj GitObject, err error) { 15 | err = r.normalizeBasename() 16 | if r.packfiles == nil { 17 | packfiles, err := r.listPackfiles() 18 | if err != nil { 19 | return nil, err 20 | } 21 | r.packfiles = packfiles 22 | } 23 | basedir := &r.Basedir 24 | if filepath.Base(basedir.Name()) != ".git" { 25 | basedirName := basedir.Name() 26 | basedir.Close() 27 | basedir, err = os.Open(filepath.Join(basedirName, ".git")) 28 | if err != nil { 29 | return nil, err 30 | } 31 | } 32 | obj, err = newObject(input, basedir, r.packfiles) 33 | return obj, err 34 | } 35 | 36 | func (r *Repository) normalizeBasename() error { 37 | var err error 38 | candidate := &r.Basedir 39 | if candidate.Name() == "" { 40 | candidate, err = os.Open(".") 41 | if err != nil { 42 | return err 43 | } 44 | } 45 | candidateName := candidate.Name() 46 | if filepath.Base(candidateName) != ".git" { 47 | candidateName = filepath.Join(candidateName, ".git") 48 | } 49 | for { 50 | candidate, err = os.Open(candidateName) 51 | if err == nil { 52 | r.Basedir = *candidate 53 | break 54 | } 55 | if !os.IsNotExist(err) { 56 | return err 57 | } 58 | 59 | // This should not be the main condition of the for loop 60 | // just in case the filesystem root directory contains 61 | // a .git subdirectory 62 | // TODO check for mountpoint 63 | if candidateName == "/.git" { 64 | return fmt.Errorf("not a git repository (or any parent up to root /") 65 | } 66 | candidateName, err = filepath.Abs(filepath.Join(candidateName, "..", "..", ".git")) 67 | candidate.Close() 68 | } 69 | return nil 70 | } 71 | 72 | // findGitDir is like normalizeBasename but does not require 73 | // a repository with a valid file descriptor to operate on. 74 | // If pwd is non-nil, it will use the provided file. Otherwise, 75 | // it will default to the current working directory. 76 | func findGitDir(pwd *os.File) (dir *os.File, err error) { 77 | if pwd == nil { 78 | pwd, err = os.Open(".") 79 | if err != nil { 80 | return nil, err 81 | } 82 | } 83 | 84 | candidate := pwd 85 | candidateName := candidate.Name() 86 | if filepath.Base(candidateName) != ".git" { 87 | candidateName = filepath.Join(candidateName, ".git") 88 | } 89 | for { 90 | candidate, err = os.Open(candidateName) 91 | if err == nil { 92 | return candidate, nil 93 | } 94 | if !os.IsNotExist(err) { 95 | return nil, err 96 | } 97 | 98 | // This should not be the main condition of the for loop 99 | // just in case the filesystem root directory contains 100 | // a .git subdirectory 101 | // TODO check for mountpoint 102 | if candidateName == "/.git" { 103 | return nil, fmt.Errorf("not a git repository (or any parent up to root /") 104 | } 105 | candidateName, err = filepath.Abs(filepath.Join(candidateName, "..", "..", ".git")) 106 | candidate.Close() 107 | } 108 | return nil, fmt.Errorf("could not find the git repository") 109 | } 110 | -------------------------------------------------------------------------------- /test_data/test-delta-new.c: -------------------------------------------------------------------------------- 1 | /* 2 | * test-delta.c: test code to exercise diff-delta.c and patch-delta.c 3 | * 4 | * (C) 2005 Nicolas Pitre 5 | * 6 | * This code is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License version 2 as 8 | * published by the Free Software Foundation. 9 | */ 10 | 11 | 12 | // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit. Donec et mollis dolor. Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam tincidunt congue enim, ut porta lorem lacinia consectetur. Donec ut libero sed arcu vehicula ultricies a non tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ut gravida lorem. Ut turpis felis, pulvinar a semper sed, adipiscing id dolor. Pellentesque auctor nisi id magna consequat sagittis. Curabitur dapibus enim sit amet elit pharetra tincidunt feugiat nisl imperdiet. Ut convallis libero in urna ultrices accumsan. Donec sed odio eros. Donec viverra mi quis quam pulvinar at malesuada arcu rhoncus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In rutrum accumsan ultricies. Mauris vitae nisi at sem facilisis semper ac in est. 13 | 14 | #include "git-compat-util.h" 15 | #include "delta.h" 16 | #include "cache.h" 17 | 18 | static const char usage_str[] = 19 | "test-delta (-d|-p) "; 20 | 21 | int main(int argc, char *argv[]) 22 | { 23 | int fd; 24 | struct stat st; 25 | void *from_buf, *data_buf, *out_buf; 26 | unsigned long from_size, data_size, out_size; 27 | 28 | if (argc != 5 || (strcmp(argv[1], "-d") && strcmp(argv[1], "-p"))) { 29 | fprintf(stderr, "usage: %s\n", usage_str); 30 | return 1; 31 | } 32 | 33 | fd = open(argv[2], O_RDONLY); 34 | if (fd < 0 || fstat(fd, &st)) { 35 | perror(argv[2]); 36 | return 1; 37 | } 38 | from_size = st.st_size; 39 | from_buf = mmap(NULL, from_size, PROT_READ, MAP_PRIVATE, fd, 0); 40 | if (from_buf == MAP_FAILED) { 41 | perror(argv[2]); 42 | close(fd); 43 | return 1; 44 | } 45 | close(fd); 46 | 47 | fd = open(argv[3], O_RDONLY); 48 | if (fd < 0 || fstat(fd, &st)) { 49 | perror(argv[3]); 50 | return 1; 51 | } 52 | data_size = st.st_size; 53 | data_buf = mmap(NULL, data_size, PROT_READ, MAP_PRIVATE, fd, 0); 54 | if (data_buf == MAP_FAILED) { 55 | perror(argv[3]); 56 | close(fd); 57 | return 1; 58 | } 59 | close(fd); 60 | 61 | if (argv[1][1] == 'd') 62 | out_buf = diff_delta(from_buf, from_size, 63 | data_buf, data_size, 64 | &out_size, 0); 65 | else 66 | out_buf = patch_delta(from_buf, from_size, 67 | data_buf, data_size, 68 | &out_size); 69 | if (!out_buf) { 70 | fprintf(stderr, "delta operation failed (returned NULL)\n"); 71 | return 1; 72 | } 73 | 74 | /* 75 | * Vivamus fermentum semper porta. Nunc diam velit, adipiscing ut tristique vitae, sagittis vel odio. Maecenas convallis ullamcorper ultricies. Curabitur ornare, ligula semper consectetur sagittis, nisi diam iaculis velit, id fringilla sem nunc vel mi. Nam dictum, odio nec pretium volutpat, arcu ante placerat erat, non tristique elit urna et turpis. Quisque mi metus, ornare sit amet fermentum et, tincidunt et orci. Fusce eget orci a orci congue vestibulum. Ut dolor diam, elementum et vestibulum eu, porttitor vel elit. Curabitur venenatis pulvinar tellus gravida ornare. Sed et erat faucibus nunc euismod ultricies ut id justo. Nullam cursus suscipit nisi, et ultrices justo sodales nec. Fusce venenatis facilisis lectus ac semper. Aliquam at massa ipsum. Quisque bibendum purus convallis nulla ultrices ultricies. Nullam aliquam, mi eu aliquam tincidunt, purus velit laoreet tortor, viverra pretium nisi quam vitae mi. Fusce vel volutpat elit. Nam sagittis nisi dui. 76 | */ 77 | fd = open (argv[4], O_WRONLY|O_CREAT|O_TRUNC, 0666); 78 | if (fd < 0 || write_in_full(fd, out_buf, out_size) != out_size) { 79 | perror(argv[4]); 80 | return 1; 81 | } 82 | 83 | return 0; 84 | } 85 | -------------------------------------------------------------------------------- /test_data/dot_git/hooks/update.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to blocks unannotated tags from entering. 4 | # Called by "git receive-pack" with arguments: refname sha1-old sha1-new 5 | # 6 | # To enable this hook, rename this file to "update". 7 | # 8 | # Config 9 | # ------ 10 | # hooks.allowunannotated 11 | # This boolean sets whether unannotated tags will be allowed into the 12 | # repository. By default they won't be. 13 | # hooks.allowdeletetag 14 | # This boolean sets whether deleting tags will be allowed in the 15 | # repository. By default they won't be. 16 | # hooks.allowmodifytag 17 | # This boolean sets whether a tag may be modified after creation. By default 18 | # it won't be. 19 | # hooks.allowdeletebranch 20 | # This boolean sets whether deleting branches will be allowed in the 21 | # repository. By default they won't be. 22 | # hooks.denycreatebranch 23 | # This boolean sets whether remotely creating branches will be denied 24 | # in the repository. By default this is allowed. 25 | # 26 | 27 | # --- Command line 28 | refname="$1" 29 | oldrev="$2" 30 | newrev="$3" 31 | 32 | # --- Safety check 33 | if [ -z "$GIT_DIR" ]; then 34 | echo "Don't run this script from the command line." >&2 35 | echo " (if you want, you could supply GIT_DIR then run" >&2 36 | echo " $0 )" >&2 37 | exit 1 38 | fi 39 | 40 | if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then 41 | echo "usage: $0 " >&2 42 | exit 1 43 | fi 44 | 45 | # --- Config 46 | allowunannotated=$(git config --bool hooks.allowunannotated) 47 | allowdeletebranch=$(git config --bool hooks.allowdeletebranch) 48 | denycreatebranch=$(git config --bool hooks.denycreatebranch) 49 | allowdeletetag=$(git config --bool hooks.allowdeletetag) 50 | allowmodifytag=$(git config --bool hooks.allowmodifytag) 51 | 52 | # check for no description 53 | projectdesc=$(sed -e '1q' "$GIT_DIR/description") 54 | case "$projectdesc" in 55 | "Unnamed repository"* | "") 56 | echo "*** Project description file hasn't been set" >&2 57 | exit 1 58 | ;; 59 | esac 60 | 61 | # --- Check types 62 | # if $newrev is 0000...0000, it's a commit to delete a ref. 63 | zero="0000000000000000000000000000000000000000" 64 | if [ "$newrev" = "$zero" ]; then 65 | newrev_type=delete 66 | else 67 | newrev_type=$(git cat-file -t $newrev) 68 | fi 69 | 70 | case "$refname","$newrev_type" in 71 | refs/tags/*,commit) 72 | # un-annotated tag 73 | short_refname=${refname##refs/tags/} 74 | if [ "$allowunannotated" != "true" ]; then 75 | echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2 76 | echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 77 | exit 1 78 | fi 79 | ;; 80 | refs/tags/*,delete) 81 | # delete tag 82 | if [ "$allowdeletetag" != "true" ]; then 83 | echo "*** Deleting a tag is not allowed in this repository" >&2 84 | exit 1 85 | fi 86 | ;; 87 | refs/tags/*,tag) 88 | # annotated tag 89 | if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 90 | then 91 | echo "*** Tag '$refname' already exists." >&2 92 | echo "*** Modifying a tag is not allowed in this repository." >&2 93 | exit 1 94 | fi 95 | ;; 96 | refs/heads/*,commit) 97 | # branch 98 | if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then 99 | echo "*** Creating a branch is not allowed in this repository" >&2 100 | exit 1 101 | fi 102 | ;; 103 | refs/heads/*,delete) 104 | # delete branch 105 | if [ "$allowdeletebranch" != "true" ]; then 106 | echo "*** Deleting a branch is not allowed in this repository" >&2 107 | exit 1 108 | fi 109 | ;; 110 | refs/remotes/*,commit) 111 | # tracking branch 112 | ;; 113 | refs/remotes/*,delete) 114 | # delete tracking branch 115 | if [ "$allowdeletebranch" != "true" ]; then 116 | echo "*** Deleting a tracking branch is not allowed in this repository" >&2 117 | exit 1 118 | fi 119 | ;; 120 | *) 121 | # Anything else (is there anything else?) 122 | echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 123 | exit 1 124 | ;; 125 | esac 126 | 127 | # --- Finished 128 | exit 0 129 | -------------------------------------------------------------------------------- /test_data/dot_git/hooks/pre-rebase.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright (c) 2006, 2008 Junio C Hamano 4 | # 5 | # The "pre-rebase" hook is run just before "git rebase" starts doing 6 | # its job, and can prevent the command from running by exiting with 7 | # non-zero status. 8 | # 9 | # The hook is called with the following parameters: 10 | # 11 | # $1 -- the upstream the series was forked from. 12 | # $2 -- the branch being rebased (or empty when rebasing the current branch). 13 | # 14 | # This sample shows how to prevent topic branches that are already 15 | # merged to 'next' branch from getting rebased, because allowing it 16 | # would result in rebasing already published history. 17 | 18 | publish=next 19 | basebranch="$1" 20 | if test "$#" = 2 21 | then 22 | topic="refs/heads/$2" 23 | else 24 | topic=`git symbolic-ref HEAD` || 25 | exit 0 ;# we do not interrupt rebasing detached HEAD 26 | fi 27 | 28 | case "$topic" in 29 | refs/heads/??/*) 30 | ;; 31 | *) 32 | exit 0 ;# we do not interrupt others. 33 | ;; 34 | esac 35 | 36 | # Now we are dealing with a topic branch being rebased 37 | # on top of master. Is it OK to rebase it? 38 | 39 | # Does the topic really exist? 40 | git show-ref -q "$topic" || { 41 | echo >&2 "No such branch $topic" 42 | exit 1 43 | } 44 | 45 | # Is topic fully merged to master? 46 | not_in_master=`git rev-list --pretty=oneline ^master "$topic"` 47 | if test -z "$not_in_master" 48 | then 49 | echo >&2 "$topic is fully merged to master; better remove it." 50 | exit 1 ;# we could allow it, but there is no point. 51 | fi 52 | 53 | # Is topic ever merged to next? If so you should not be rebasing it. 54 | only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` 55 | only_next_2=`git rev-list ^master ${publish} | sort` 56 | if test "$only_next_1" = "$only_next_2" 57 | then 58 | not_in_topic=`git rev-list "^$topic" master` 59 | if test -z "$not_in_topic" 60 | then 61 | echo >&2 "$topic is already up-to-date with master" 62 | exit 1 ;# we could allow it, but there is no point. 63 | else 64 | exit 0 65 | fi 66 | else 67 | not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` 68 | /usr/bin/perl -e ' 69 | my $topic = $ARGV[0]; 70 | my $msg = "* $topic has commits already merged to public branch:\n"; 71 | my (%not_in_next) = map { 72 | /^([0-9a-f]+) /; 73 | ($1 => 1); 74 | } split(/\n/, $ARGV[1]); 75 | for my $elem (map { 76 | /^([0-9a-f]+) (.*)$/; 77 | [$1 => $2]; 78 | } split(/\n/, $ARGV[2])) { 79 | if (!exists $not_in_next{$elem->[0]}) { 80 | if ($msg) { 81 | print STDERR $msg; 82 | undef $msg; 83 | } 84 | print STDERR " $elem->[1]\n"; 85 | } 86 | } 87 | ' "$topic" "$not_in_next" "$not_in_master" 88 | exit 1 89 | fi 90 | 91 | exit 0 92 | 93 | ################################################################ 94 | 95 | This sample hook safeguards topic branches that have been 96 | published from being rewound. 97 | 98 | The workflow assumed here is: 99 | 100 | * Once a topic branch forks from "master", "master" is never 101 | merged into it again (either directly or indirectly). 102 | 103 | * Once a topic branch is fully cooked and merged into "master", 104 | it is deleted. If you need to build on top of it to correct 105 | earlier mistakes, a new topic branch is created by forking at 106 | the tip of the "master". This is not strictly necessary, but 107 | it makes it easier to keep your history simple. 108 | 109 | * Whenever you need to test or publish your changes to topic 110 | branches, merge them into "next" branch. 111 | 112 | The script, being an example, hardcodes the publish branch name 113 | to be "next", but it is trivial to make it configurable via 114 | $GIT_DIR/config mechanism. 115 | 116 | With this workflow, you would want to know: 117 | 118 | (1) ... if a topic branch has ever been merged to "next". Young 119 | topic branches can have stupid mistakes you would rather 120 | clean up before publishing, and things that have not been 121 | merged into other branches can be easily rebased without 122 | affecting other people. But once it is published, you would 123 | not want to rewind it. 124 | 125 | (2) ... if a topic branch has been fully merged to "master". 126 | Then you can delete it. More importantly, you should not 127 | build on top of it -- other people may already want to 128 | change things related to the topic as patches against your 129 | "master", so if you need further changes, it is better to 130 | fork the topic (perhaps with the same name) afresh from the 131 | tip of "master". 132 | 133 | Let's look at this example: 134 | 135 | o---o---o---o---o---o---o---o---o---o "next" 136 | / / / / 137 | / a---a---b A / / 138 | / / / / 139 | / / c---c---c---c B / 140 | / / / \ / 141 | / / / b---b C \ / 142 | / / / / \ / 143 | ---o---o---o---o---o---o---o---o---o---o---o "master" 144 | 145 | 146 | A, B and C are topic branches. 147 | 148 | * A has one fix since it was merged up to "next". 149 | 150 | * B has finished. It has been fully merged up to "master" and "next", 151 | and is ready to be deleted. 152 | 153 | * C has not merged to "next" at all. 154 | 155 | We would want to allow C to be rebased, refuse A, and encourage 156 | B to be deleted. 157 | 158 | To compute (1): 159 | 160 | git rev-list ^master ^topic next 161 | git rev-list ^master next 162 | 163 | if these match, topic has not merged in next at all. 164 | 165 | To compute (2): 166 | 167 | git rev-list master..topic 168 | 169 | if this is empty, it is fully merged to "master". 170 | -------------------------------------------------------------------------------- /pack.go: -------------------------------------------------------------------------------- 1 | package gitgo 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | "path/filepath" 9 | "strconv" 10 | "strings" 11 | ) 12 | 13 | type packfile struct { 14 | basedir os.File 15 | name SHA 16 | objects map[SHA]*packObject 17 | } 18 | 19 | func (p *packfile) verify() error { 20 | if p.objects == nil { 21 | p.objects = map[SHA]*packObject{} 22 | } 23 | packf, err := os.Open(filepath.Join(p.basedir.Name(), "objects", "pack", string(p.name)+".pack")) 24 | if err != nil { 25 | return err 26 | } 27 | defer packf.Close() 28 | idxf, err := os.Open(filepath.Join(p.basedir.Name(), "objects", "pack", string(p.name)+".idx")) 29 | if err != nil { 30 | return err 31 | } 32 | defer idxf.Close() 33 | objs, err := VerifyPack(packf, idxf) 34 | if err != nil { 35 | return err 36 | } 37 | for _, obj := range objs { 38 | p.objects[obj.Name] = obj 39 | } 40 | return nil 41 | } 42 | 43 | type packObject struct { 44 | Name SHA 45 | Offset int 46 | Data []byte 47 | _type packObjectType 48 | PatchedData []byte 49 | 50 | Size int // the uncompressed size 51 | 52 | SizeInPackfile int // the compressed size 53 | 54 | // only used for OBJ_OFS_DELTA 55 | negativeOffset int 56 | BaseObjectName SHA 57 | BaseObjectType packObjectType 58 | baseOffset int 59 | Depth int 60 | 61 | err error // was an error encountered while processing this object? 62 | } 63 | 64 | func (p *packObject) Type() string { 65 | switch p.BaseObjectType { 66 | case OBJ_COMMIT: 67 | return "commit" 68 | case OBJ_TREE: 69 | return "tree" 70 | case OBJ_BLOB: 71 | return "blob" 72 | default: 73 | return p.BaseObjectType.String() 74 | } 75 | } 76 | 77 | // normalize returns a GitObject equivalent to the packObject. 78 | // packObject satisfies the GitObject interface, but if the pack 79 | // object type is a commit, tree, or blob, it will return a Commit, 80 | // Tree, or Blob struct instead of the packObject 81 | func (p *packObject) normalize(basedir os.File) (GitObject, error) { 82 | switch p.BaseObjectType { 83 | case OBJ_COMMIT: 84 | return p.Commit(basedir) 85 | case OBJ_TREE: 86 | return p.Tree(basedir) 87 | case OBJ_BLOB: 88 | return p.Blob(basedir) 89 | default: 90 | return p, nil 91 | } 92 | } 93 | 94 | // Commit returns a Commit struct for the packObject. 95 | func (p *packObject) Commit(basedir os.File) (Commit, error) { 96 | if p.BaseObjectType != OBJ_COMMIT { 97 | return Commit{}, fmt.Errorf("pack object is not a commit: %s", p.Type()) 98 | } 99 | if p.PatchedData == nil { 100 | p.PatchedData = p.Data 101 | } 102 | 103 | commit, err := parseCommit(bytes.NewReader(p.PatchedData), strconv.Itoa(p.Size), p.Name) 104 | commit.rawData = p.PatchedData 105 | return commit, err 106 | } 107 | 108 | // Tree returns a Tree struct for the packObject. 109 | func (p *packObject) Tree(basedir os.File) (Tree, error) { 110 | if p.BaseObjectType != OBJ_TREE { 111 | return Tree{}, fmt.Errorf("pack object is not a tree: %s", p.Type()) 112 | } 113 | if p.PatchedData == nil { 114 | p.PatchedData = p.Data 115 | } 116 | 117 | tree, err := parseTree(bytes.NewReader(p.PatchedData), strconv.Itoa(p.Size), basedir) 118 | return tree, err 119 | } 120 | 121 | // Blob returns a Blob struct for the packObject. 122 | func (p *packObject) Blob(basedir os.File) (Blob, error) { 123 | if p.BaseObjectType != OBJ_BLOB { 124 | return Blob{}, fmt.Errorf("pack object is not a blob: %s", p.Type()) 125 | } 126 | if p.PatchedData == nil { 127 | p.PatchedData = p.Data 128 | } 129 | 130 | // TODO fix the size param 131 | blob, err := parseBlob(bytes.NewReader(p.PatchedData), "") 132 | blob.rawData = p.PatchedData 133 | return blob, err 134 | } 135 | 136 | func (p *packObject) Patch(dict map[SHA]*packObject) error { 137 | if len(p.PatchedData) != 0 { 138 | return nil 139 | } 140 | if p._type < OBJ_OFS_DELTA { 141 | if p.Data == nil { 142 | return fmt.Errorf("base object data is nil") 143 | } 144 | p.PatchedData = p.Data 145 | p.BaseObjectType = p._type 146 | return nil 147 | } 148 | 149 | if p._type >= OBJ_OFS_DELTA { 150 | base, ok := dict[p.BaseObjectName] 151 | if !ok { 152 | return fmt.Errorf("base object not in dictionary: %s", p.BaseObjectName) 153 | } 154 | err := base.Patch(dict) 155 | if err != nil { 156 | return err 157 | } 158 | 159 | // At the time patchDelta is called, we know that the base.PatchedData is non-nil 160 | patched, err := patchDelta(bytes.NewReader(base.PatchedData), bytes.NewReader(p.Data)) 161 | if err != nil { 162 | return err 163 | } 164 | 165 | p.PatchedData, err = ioutil.ReadAll(patched) 166 | if err != nil { 167 | return err 168 | } 169 | 170 | p.BaseObjectType = base.BaseObjectType 171 | p.Depth += base.Depth 172 | } 173 | return nil 174 | } 175 | 176 | func (p *packObject) PatchedType() packObjectType { 177 | if p._type < OBJ_OFS_DELTA { 178 | return p._type 179 | } 180 | return p.BaseObjectType 181 | } 182 | 183 | //go:generate stringer -type=packObjectType 184 | type packObjectType uint8 185 | 186 | const ( 187 | _ packObjectType = iota 188 | OBJ_COMMIT 189 | OBJ_TREE 190 | OBJ_BLOB 191 | OBJ_TAG 192 | _ 193 | OBJ_OFS_DELTA 194 | OBJ_REF_DELTA 195 | ) 196 | 197 | func (r *Repository) listPackfiles() ([]*packfile, error) { 198 | basedir := r.Basedir 199 | files, err := ioutil.ReadDir(filepath.Join(basedir.Name(), "objects", "pack")) 200 | if err != nil { 201 | return nil, err 202 | } 203 | packfileNames := []SHA{} 204 | for _, file := range files { 205 | base := strings.TrimSuffix(file.Name(), ".pack") 206 | if base == file.Name() { 207 | // this wasn't a packfile 208 | continue 209 | } 210 | packfileNames = append(packfileNames, SHA(base)) 211 | } 212 | packs := make([]*packfile, len(packfileNames)) 213 | for i, n := range packfileNames { 214 | p := &packfile{basedir: basedir, name: n} 215 | err = p.verify() 216 | if err != nil { 217 | return nil, err 218 | } 219 | packs[i] = p 220 | } 221 | return packs, nil 222 | } 223 | -------------------------------------------------------------------------------- /delta.go: -------------------------------------------------------------------------------- 1 | package gitgo 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "os" 8 | ) 9 | 10 | // patchDelta will apply a delta to a base. 11 | func patchDelta(start io.ReadSeeker, delta io.Reader) (io.Reader, error) { 12 | base := errReadSeeker{start, nil} 13 | deltar := newErrReader(delta) 14 | 15 | // First, read the source and target lengths (varints) 16 | // we can ignore err as long as we check deltar.err at the end 17 | // we have no need for the targetLength right now, so we discard it 18 | sourceLength, _ := parseVarInt(deltar) 19 | _, _ = parseVarInt(deltar) 20 | if deltar.err != nil { 21 | return nil, deltar.err 22 | } 23 | 24 | result := bytes.NewBuffer(nil) 25 | //result = io.MultiWriter(result, os.Stderr) 26 | 27 | // Now, the rest of the bytes are either copy or insert instructions 28 | // If the MSB is set, it is a copy 29 | 30 | for { 31 | bs := make([]byte, 1) 32 | n := deltar.read(bs) 33 | if n == 0 { 34 | break 35 | } 36 | 37 | // b represents our command itself (opcode) 38 | b := bs[0] 39 | switch b & 128 { 40 | case 128: 41 | // b is a copy instruction 42 | 43 | // the last four bits represent the offset from the base (source) 44 | var baseOffset int 45 | if (b & 1) > 0 { 46 | baseOffset = baseOffset | int(uint(deltar.readByte())) 47 | } 48 | 49 | if (b & 2) > 0 { 50 | baseOffset = baseOffset | int((uint(deltar.readByte()) << 8)) 51 | } 52 | 53 | if (b & 4) > 0 { 54 | baseOffset = baseOffset | int((uint(deltar.readByte()) << 16)) 55 | } 56 | 57 | if (b & 8) > 0 { 58 | baseOffset = baseOffset | int((uint(deltar.readByte()) << 24)) 59 | } 60 | 61 | // read the number of bytes to copy from source→target 62 | // if the fifth bit from the right is set, read the next byte 63 | // The number of copy bytes must fit into 64 | 65 | var numBytes uint 66 | 67 | if (b & 16) > 0 { 68 | numBytes = numBytes | uint(uint(deltar.readByte())) 69 | } 70 | if (b & 32) > 0 { 71 | numBytes = numBytes | uint((uint(deltar.readByte()) << 8)) 72 | } 73 | 74 | if (b & 64) > 0 { 75 | numBytes = numBytes | uint((uint(deltar.readByte()) << 16)) 76 | } 77 | 78 | // Default to 0x10000 due to overflow 79 | if numBytes == 0 { 80 | numBytes = 65536 81 | } 82 | 83 | // read numBytes from source, starting at baseOffset 84 | // and write that to the target 85 | base.Seek(int64(baseOffset), os.SEEK_SET) 86 | buf := make([]byte, numBytes) 87 | base.read(buf) 88 | 89 | _, err := result.Write(buf) 90 | if err != nil { 91 | return result, err 92 | } 93 | 94 | case 0: 95 | if b == 0 { 96 | // cmd == 0 is reserved for future encoding extensions 97 | return nil, fmt.Errorf("cannot process delta opcode 0") 98 | } 99 | 100 | // insert instruction 101 | // this means we write data directly from delta to the target 102 | 103 | // b itself tells us the number of bytes to write to the target 104 | // the MSB is not set, so the maximum number to insert is 127 bytes 105 | 106 | numBytes := int(b) 107 | buf := make([]byte, numBytes) 108 | deltar.read(buf) 109 | _, err := result.Write(buf) 110 | if err != nil { 111 | return nil, err 112 | } 113 | 114 | default: 115 | return nil, fmt.Errorf("invalid opcode %08b", b) 116 | } 117 | } 118 | 119 | n, err := base.Seek(0, os.SEEK_END) 120 | if err != nil { 121 | return nil, err 122 | } 123 | if n != int64(sourceLength) { 124 | return nil, fmt.Errorf("expected to read %d bytes and read %d", sourceLength, n) 125 | } 126 | 127 | if deltar.err == io.EOF { 128 | return result, nil 129 | } 130 | return result, deltar.err 131 | } 132 | 133 | func parseVarInt(r io.Reader) (int, error) { 134 | // The MSB of the first byte indicates whether to read 135 | // the next byte 136 | 137 | _bytes := make([]byte, 1) 138 | _, err := r.Read(_bytes) 139 | if err != nil { 140 | return 0, err 141 | } 142 | _byte := _bytes[0] 143 | 144 | // The most-significant byte (MSB) 145 | // tells us whether we need to read more bytes 146 | // to get the encoded object size 147 | MSB := (_byte & 128) // will be either 128 or 0 148 | 149 | // This will extract the last seven bits of the byte 150 | var objectSize = int((uint(_byte) & 127)) 151 | 152 | // shift the first size by 0 153 | // and the rest by (i-1) * 7 154 | var shift uint 155 | 156 | // If the most-significant bit is 0, this is the last byte 157 | // for the object size 158 | for MSB > 0 { 159 | shift += 7 160 | // Keep reading the size until the MSB is 0 161 | _bytes := make([]byte, 1) 162 | _, err := r.Read(_bytes) 163 | if err != nil { 164 | return 0, err 165 | } 166 | _byte := _bytes[0] 167 | 168 | MSB = (_byte & 128) 169 | 170 | objectSize += int((uint(_byte) & 127) << shift) 171 | } 172 | return objectSize, nil 173 | } 174 | 175 | type errReader struct { 176 | r io.Reader 177 | n int 178 | err error 179 | } 180 | 181 | // Turn an io.Reader into an errReader. 182 | // If r is already an io.Reader, this is a no-op 183 | func newErrReader(r io.Reader) errReader { 184 | er, ok := r.(errReader) 185 | if !ok { 186 | er = errReader{r, 0, nil} 187 | } 188 | return er 189 | } 190 | 191 | // Read, but only if no errors have been encountered 192 | // in a previous read (including io.EOF) 193 | func (er *errReader) read(buf []byte) int { 194 | var n int 195 | if er.err != nil { 196 | return 0 197 | } 198 | n, er.err = io.ReadFull(er.r, buf) 199 | er.n += n 200 | return n 201 | } 202 | 203 | // Like read(), but expect a single byte 204 | func (er *errReader) readByte() byte { 205 | b := make([]byte, 1) 206 | n := er.read(b) 207 | if n != 1 && er.err != nil { 208 | if er.err == io.EOF { 209 | er.err = io.EOF 210 | return b[0] 211 | } 212 | er.err = fmt.Errorf("expected to read single byte and read none") 213 | } 214 | return b[0] 215 | } 216 | 217 | // Should not actually be called 218 | // Defined only to ensure that errReader is itself an ioReader 219 | func (er errReader) Read(buf []byte) (int, error) { 220 | n := er.read(buf) 221 | return n, er.err 222 | } 223 | -------------------------------------------------------------------------------- /test_data/zlib.c: -------------------------------------------------------------------------------- 1 | /* 2 | * zlib wrappers to make sure we don't silently miss errors 3 | * at init time. 4 | */ 5 | #include "cache.h" 6 | 7 | static const char *zerr_to_string(int status) 8 | { 9 | switch (status) { 10 | case Z_MEM_ERROR: 11 | return "out of memory"; 12 | case Z_VERSION_ERROR: 13 | return "wrong version"; 14 | case Z_NEED_DICT: 15 | return "needs dictionary"; 16 | case Z_DATA_ERROR: 17 | return "data stream error"; 18 | case Z_STREAM_ERROR: 19 | return "stream consistency error"; 20 | default: 21 | return "unknown error"; 22 | } 23 | } 24 | 25 | /* 26 | * avail_in and avail_out in zlib are counted in uInt, which typically 27 | * limits the size of the buffer we can use to 4GB when interacting 28 | * with zlib in a single call to inflate/deflate. 29 | */ 30 | /* #define ZLIB_BUF_MAX ((uInt)-1) */ 31 | #define ZLIB_BUF_MAX ((uInt) 1024 * 1024 * 1024) /* 1GB */ 32 | static inline uInt zlib_buf_cap(unsigned long len) 33 | { 34 | return (ZLIB_BUF_MAX < len) ? ZLIB_BUF_MAX : len; 35 | } 36 | 37 | static void zlib_pre_call(git_zstream *s) 38 | { 39 | s->z.next_in = s->next_in; 40 | s->z.next_out = s->next_out; 41 | s->z.total_in = s->total_in; 42 | s->z.total_out = s->total_out; 43 | s->z.avail_in = zlib_buf_cap(s->avail_in); 44 | s->z.avail_out = zlib_buf_cap(s->avail_out); 45 | } 46 | 47 | static void zlib_post_call(git_zstream *s) 48 | { 49 | unsigned long bytes_consumed; 50 | unsigned long bytes_produced; 51 | 52 | bytes_consumed = s->z.next_in - s->next_in; 53 | bytes_produced = s->z.next_out - s->next_out; 54 | if (s->z.total_out != s->total_out + bytes_produced) 55 | die("BUG: total_out mismatch"); 56 | if (s->z.total_in != s->total_in + bytes_consumed) 57 | die("BUG: total_in mismatch"); 58 | 59 | s->total_out = s->z.total_out; 60 | s->total_in = s->z.total_in; 61 | s->next_in = s->z.next_in; 62 | s->next_out = s->z.next_out; 63 | s->avail_in -= bytes_consumed; 64 | s->avail_out -= bytes_produced; 65 | } 66 | 67 | void git_inflate_init(git_zstream *strm) 68 | { 69 | int status; 70 | 71 | zlib_pre_call(strm); 72 | status = inflateInit(&strm->z); 73 | zlib_post_call(strm); 74 | if (status == Z_OK) 75 | return; 76 | die("inflateInit: %s (%s)", zerr_to_string(status), 77 | strm->z.msg ? strm->z.msg : "no message"); 78 | } 79 | 80 | void git_inflate_init_gzip_only(git_zstream *strm) 81 | { 82 | /* 83 | * Use default 15 bits, +16 is to accept only gzip and to 84 | * yield Z_DATA_ERROR when fed zlib format. 85 | */ 86 | const int windowBits = 15 + 16; 87 | int status; 88 | 89 | zlib_pre_call(strm); 90 | status = inflateInit2(&strm->z, windowBits); 91 | zlib_post_call(strm); 92 | if (status == Z_OK) 93 | return; 94 | die("inflateInit2: %s (%s)", zerr_to_string(status), 95 | strm->z.msg ? strm->z.msg : "no message"); 96 | } 97 | 98 | void git_inflate_end(git_zstream *strm) 99 | { 100 | int status; 101 | 102 | zlib_pre_call(strm); 103 | status = inflateEnd(&strm->z); 104 | zlib_post_call(strm); 105 | if (status == Z_OK) 106 | return; 107 | error("inflateEnd: %s (%s)", zerr_to_string(status), 108 | strm->z.msg ? strm->z.msg : "no message"); 109 | } 110 | 111 | int git_inflate(git_zstream *strm, int flush) 112 | { 113 | int status; 114 | 115 | for (;;) { 116 | zlib_pre_call(strm); 117 | /* Never say Z_FINISH unless we are feeding everything */ 118 | status = inflate(&strm->z, 119 | (strm->z.avail_in != strm->avail_in) 120 | ? 0 : flush); 121 | if (status == Z_MEM_ERROR) 122 | die("inflate: out of memory"); 123 | zlib_post_call(strm); 124 | 125 | /* 126 | * Let zlib work another round, while we can still 127 | * make progress. 128 | */ 129 | if ((strm->avail_out && !strm->z.avail_out) && 130 | (status == Z_OK || status == Z_BUF_ERROR)) 131 | continue; 132 | break; 133 | } 134 | 135 | switch (status) { 136 | /* Z_BUF_ERROR: normal, needs more space in the output buffer */ 137 | case Z_BUF_ERROR: 138 | case Z_OK: 139 | case Z_STREAM_END: 140 | return status; 141 | default: 142 | break; 143 | } 144 | error("inflate: %s (%s)", zerr_to_string(status), 145 | strm->z.msg ? strm->z.msg : "no message"); 146 | return status; 147 | } 148 | 149 | #if defined(NO_DEFLATE_BOUND) || ZLIB_VERNUM < 0x1200 150 | #define deflateBound(c,s) ((s) + (((s) + 7) >> 3) + (((s) + 63) >> 6) + 11) 151 | #endif 152 | 153 | unsigned long git_deflate_bound(git_zstream *strm, unsigned long size) 154 | { 155 | return deflateBound(&strm->z, size); 156 | } 157 | 158 | void git_deflate_init(git_zstream *strm, int level) 159 | { 160 | int status; 161 | 162 | memset(strm, 0, sizeof(*strm)); 163 | zlib_pre_call(strm); 164 | status = deflateInit(&strm->z, level); 165 | zlib_post_call(strm); 166 | if (status == Z_OK) 167 | return; 168 | die("deflateInit: %s (%s)", zerr_to_string(status), 169 | strm->z.msg ? strm->z.msg : "no message"); 170 | } 171 | 172 | static void do_git_deflate_init(git_zstream *strm, int level, int windowBits) 173 | { 174 | int status; 175 | 176 | memset(strm, 0, sizeof(*strm)); 177 | zlib_pre_call(strm); 178 | status = deflateInit2(&strm->z, level, 179 | Z_DEFLATED, windowBits, 180 | 8, Z_DEFAULT_STRATEGY); 181 | zlib_post_call(strm); 182 | if (status == Z_OK) 183 | return; 184 | die("deflateInit2: %s (%s)", zerr_to_string(status), 185 | strm->z.msg ? strm->z.msg : "no message"); 186 | } 187 | 188 | void git_deflate_init_gzip(git_zstream *strm, int level) 189 | { 190 | /* 191 | * Use default 15 bits, +16 is to generate gzip header/trailer 192 | * instead of the zlib wrapper. 193 | */ 194 | do_git_deflate_init(strm, level, 15 + 16); 195 | } 196 | 197 | void git_deflate_init_raw(git_zstream *strm, int level) 198 | { 199 | /* 200 | * Use default 15 bits, negate the value to get raw compressed 201 | * data without zlib header and trailer. 202 | */ 203 | do_git_deflate_init(strm, level, -15); 204 | } 205 | 206 | int git_deflate_abort(git_zstream *strm) 207 | { 208 | int status; 209 | 210 | zlib_pre_call(strm); 211 | status = deflateEnd(&strm->z); 212 | zlib_post_call(strm); 213 | return status; 214 | } 215 | 216 | void git_deflate_end(git_zstream *strm) 217 | { 218 | int status = git_deflate_abort(strm); 219 | 220 | if (status == Z_OK) 221 | return; 222 | error("deflateEnd: %s (%s)", zerr_to_string(status), 223 | strm->z.msg ? strm->z.msg : "no message"); 224 | } 225 | 226 | int git_deflate_end_gently(git_zstream *strm) 227 | { 228 | int status; 229 | 230 | zlib_pre_call(strm); 231 | status = deflateEnd(&strm->z); 232 | zlib_post_call(strm); 233 | return status; 234 | } 235 | 236 | int git_deflate(git_zstream *strm, int flush) 237 | { 238 | int status; 239 | 240 | for (;;) { 241 | zlib_pre_call(strm); 242 | 243 | /* Never say Z_FINISH unless we are feeding everything */ 244 | status = deflate(&strm->z, 245 | (strm->z.avail_in != strm->avail_in) 246 | ? 0 : flush); 247 | if (status == Z_MEM_ERROR) 248 | die("deflate: out of memory"); 249 | zlib_post_call(strm); 250 | 251 | /* 252 | * Let zlib work another round, while we can still 253 | * make progress. 254 | */ 255 | if ((strm->avail_out && !strm->z.avail_out) && 256 | (status == Z_OK || status == Z_BUF_ERROR)) 257 | continue; 258 | break; 259 | } 260 | 261 | switch (status) { 262 | /* Z_BUF_ERROR: normal, needs more space in the output buffer */ 263 | case Z_BUF_ERROR: 264 | case Z_OK: 265 | case Z_STREAM_END: 266 | return status; 267 | default: 268 | break; 269 | } 270 | error("deflate: %s (%s)", zerr_to_string(status), 271 | strm->z.msg ? strm->z.msg : "no message"); 272 | return status; 273 | } 274 | -------------------------------------------------------------------------------- /test_data/zlib-changed.c: -------------------------------------------------------------------------------- 1 | /* 2 | * zlib wrappers to make sure we don't silently miss errors 3 | * at init time. 4 | */ 5 | #include "cache.h" 6 | 7 | static const char *zerr_to_string(int status) 8 | { 9 | switch (status) { 10 | case Z_MEM_ERROR: 11 | return "out of memory"; 12 | case Z_VERSION_ERROR: 13 | return "wrong version"; 14 | case Z_NEED_DICT: 15 | return "needs dictionary"; 16 | case Z_DATA_ERROR: 17 | return "data stream error"; 18 | case Z_STREAM_ERROR: 19 | return "stream consistency error"; 20 | default: 21 | return "unknown error"; 22 | } 23 | } 24 | 25 | 26 | // Add a new comment for testing 27 | 28 | 29 | /* 30 | * avail_in and avail_out in zlib are counted in uInt, which typically 31 | * limits the size of the buffer we can use to 4GB when interacting 32 | * with zlib in a single call to inflate/deflate. 33 | */ 34 | /* #define ZLIB_BUF_MAX ((uInt)-1) */ 35 | #define ZLIB_BUF_MAX ((uInt) 1024 * 1024 * 1024) /* 1GB */ 36 | static inline uInt zlib_buf_cap(unsigned long len) 37 | { 38 | return (ZLIB_BUF_MAX < len) ? ZLIB_BUF_MAX : len; 39 | } 40 | 41 | static void zlib_pre_call(git_zstream *s) 42 | { 43 | s->z.next_in = s->next_in; 44 | s->z.next_out = s->next_out; 45 | s->z.total_in = s->total_in; 46 | s->z.total_out = s->total_out; 47 | s->z.avail_in = zlib_buf_cap(s->avail_in); 48 | s->z.avail_out = zlib_buf_cap(s->avail_out); 49 | } 50 | 51 | // move this function to the end 52 | 53 | 54 | void git_inflate_init(git_zstream *strm) 55 | { 56 | int status; 57 | 58 | zlib_pre_call(strm); 59 | status = inflateInit(&strm->z); 60 | zlib_post_call(strm); 61 | if (status == Z_OK) 62 | return; 63 | die("inflateInit: %s (%s)", zerr_to_string(status), 64 | strm->z.msg ? strm->z.msg : "no message"); 65 | } 66 | 67 | void git_inflate_init_gzip_only(git_zstream *strm) 68 | { 69 | /* 70 | * Use default 15 bits, +16 is to accept only gzip and to 71 | * yield Z_DATA_ERROR when fed zlib format. 72 | */ 73 | const int windowBits = 15 + 16; 74 | int status; 75 | 76 | zlib_pre_call(strm); 77 | status = inflateInit2(&strm->z, windowBits); 78 | zlib_post_call(strm); 79 | if (status == Z_OK) 80 | return; 81 | die("inflateInit2: %s (%s)", zerr_to_string(status), 82 | strm->z.msg ? strm->z.msg : "no message"); 83 | } 84 | 85 | void git_inflate_end(git_zstream *strm) 86 | { 87 | int status; 88 | 89 | zlib_pre_call(strm); 90 | status = inflateEnd(&strm->z); 91 | zlib_post_call(strm); 92 | if (status == Z_OK) 93 | return; 94 | error("inflateEnd: %s (%s)", zerr_to_string(status), 95 | strm->z.msg ? strm->z.msg : "no message"); 96 | } 97 | 98 | int git_inflate(git_zstream *strm, int flush) 99 | { 100 | int status; 101 | 102 | for (;;) { 103 | zlib_pre_call(strm); 104 | /* Never say Z_FINISH unless we are feeding everything */ 105 | status = inflate(&strm->z, 106 | (strm->z.avail_in != strm->avail_in) 107 | ? 0 : flush); 108 | if (status == Z_MEM_ERROR) 109 | die("inflate: out of memory"); 110 | zlib_post_call(strm); 111 | 112 | /* 113 | * Let zlib work another round, while we can still 114 | * make progress. 115 | */ 116 | if ((strm->avail_out && !strm->z.avail_out) && 117 | (status == Z_OK || status == Z_BUF_ERROR)) 118 | continue; 119 | break; 120 | } 121 | 122 | switch (status) { 123 | /* Z_BUF_ERROR: normal, needs more space in the output buffer */ 124 | case Z_BUF_ERROR: 125 | case Z_OK: 126 | case Z_STREAM_END: 127 | return status; 128 | default: 129 | break; 130 | } 131 | error("inflate: %s (%s)", zerr_to_string(status), 132 | strm->z.msg ? strm->z.msg : "no message"); 133 | return status; 134 | } 135 | 136 | #if defined(NO_DEFLATE_BOUND) || ZLIB_VERNUM < 0x1200 137 | #define deflateBound(c,s) ((s) + (((s) + 7) >> 3) + (((s) + 63) >> 6) + 11) 138 | #endif 139 | 140 | unsigned long git_deflate_bound(git_zstream *strm, unsigned long size) 141 | { 142 | return deflateBound(&strm->z, size); 143 | } 144 | 145 | void git_deflate_init(git_zstream *strm, int level) 146 | { 147 | int status; 148 | 149 | memset(strm, 0, sizeof(*strm)); 150 | zlib_pre_call(strm); 151 | status = deflateInit(&strm->z, level); 152 | zlib_post_call(strm); 153 | if (status == Z_OK) 154 | return; 155 | die("deflateInit: %s (%s)", zerr_to_string(status), 156 | strm->z.msg ? strm->z.msg : "no message"); 157 | } 158 | 159 | static void do_git_deflate_init(git_zstream *strm, int level, int windowBits) 160 | { 161 | int status; 162 | 163 | memset(strm, 0, sizeof(*strm)); 164 | zlib_pre_call(strm); 165 | status = deflateInit2(&strm->z, level, 166 | Z_DEFLATED, windowBits, 167 | 8, Z_DEFAULT_STRATEGY); 168 | zlib_post_call(strm); 169 | if (status == Z_OK) 170 | return; 171 | die("deflateInit2: %s (%s)", zerr_to_string(status), 172 | strm->z.msg ? strm->z.msg : "no message"); 173 | } 174 | 175 | void git_deflate_init_gzip(git_zstream *strm, int level) 176 | { 177 | /* 178 | * Use default 15 bits, +16 is to generate gzip header/trailer 179 | * instead of the zlib wrapper. 180 | */ 181 | do_git_deflate_init(strm, level, 15 + 16); 182 | } 183 | 184 | void git_deflate_init_raw(git_zstream *strm, int level) 185 | { 186 | /* 187 | * Use default 15 bits, negate the value to get raw compressed 188 | * data without zlib header and trailer. 189 | */ 190 | do_git_deflate_init(strm, level, -15); 191 | } 192 | 193 | int git_deflate_abort(git_zstream *strm) 194 | { 195 | int status; 196 | 197 | zlib_pre_call(strm); 198 | status = deflateEnd(&strm->z); 199 | zlib_post_call(strm); 200 | return status; 201 | } 202 | 203 | void git_deflate_end(git_zstream *strm) 204 | { 205 | int status = git_deflate_abort(strm); 206 | 207 | if (status == Z_OK) 208 | return; 209 | error("deflateEnd: %s (%s)", zerr_to_string(status), 210 | strm->z.msg ? strm->z.msg : "no message"); 211 | } 212 | 213 | int git_deflate_end_gently(git_zstream *strm) 214 | { 215 | int status; 216 | 217 | zlib_pre_call(strm); 218 | status = deflateEnd(&strm->z); 219 | zlib_post_call(strm); 220 | return status; 221 | } 222 | 223 | int git_deflate(git_zstream *strm, int flush) 224 | { 225 | int status; 226 | 227 | for (;;) { 228 | zlib_pre_call(strm); 229 | 230 | /* Never say Z_FINISH unless we are feeding everything */ 231 | status = deflate(&strm->z, 232 | (strm->z.avail_in != strm->avail_in) 233 | ? 0 : flush); 234 | if (status == Z_MEM_ERROR) 235 | die("deflate: out of memory"); 236 | zlib_post_call(strm); 237 | 238 | /* 239 | * Let zlib work another round, while we can still 240 | * make progress. 241 | */ 242 | if ((strm->avail_out && !strm->z.avail_out) && 243 | (status == Z_OK || status == Z_BUF_ERROR)) 244 | continue; 245 | break; 246 | } 247 | 248 | switch (status) { 249 | /* Z_BUF_ERROR: normal, needs more space in the output buffer */ 250 | case Z_BUF_ERROR: 251 | case Z_OK: 252 | case Z_STREAM_END: 253 | return status; 254 | default: 255 | break; 256 | } 257 | error("deflate: %s (%s)", zerr_to_string(status), 258 | strm->z.msg ? strm->z.msg : "no message"); 259 | return status; 260 | } 261 | 262 | 263 | static void zlib_post_call(git_zstream *s) 264 | { 265 | unsigned long bytes_consumed; 266 | unsigned long bytes_produced; 267 | 268 | bytes_consumed = s->z.next_in - s->next_in; 269 | bytes_produced = s->z.next_out - s->next_out; 270 | if (s->z.total_out != s->total_out + bytes_produced) 271 | die("BUG: total_out mismatch"); 272 | if (s->z.total_in != s->total_in + bytes_consumed) 273 | die("BUG: total_in mismatch"); 274 | 275 | s->total_out = s->z.total_out; 276 | s->total_in = s->z.total_in; 277 | s->next_in = s->z.next_in; 278 | s->next_out = s->z.next_out; 279 | s->avail_in -= bytes_consumed; 280 | s->avail_out -= bytes_produced; 281 | } 282 | -------------------------------------------------------------------------------- /verify-pack_test.go: -------------------------------------------------------------------------------- 1 | package gitgo 2 | 3 | import ( 4 | "io" 5 | "log" 6 | "os" 7 | "path" 8 | "testing" 9 | ) 10 | 11 | func Test_VerifyPack(t *testing.T) { 12 | 13 | // only used for git verify-pack -v 14 | const expected = `fe89ee30bbcdfdf376beae530cc53f967012f31c commit 267 184 12 15 | 3ead3116d0378089f5ce61086354aac43e736b01 commit 243 170 196 16 | 1d833eb5b6c5369c0cb7a4a3e20ded237490145f commit 262 180 366 17 | a7f92c920ce85f07a33f948aa4fa2548b270024f commit 250 172 546 18 | 97eed02ebe122df8fdd853c1215d8775f3d9f1a1 commit 190 132 718 19 | d22fc8a57073fdecae2001d00aff921440d3aabd tree 121 115 850 20 | df891299372c34b57e41cfc50a0113e2afac3210 tree 25 37 965 1 d22fc8a57073fdecae2001d00aff921440d3aabd 21 | af6e4fe91a8f9a0f3c03cbec9e1d2aac47345d67 blob 18 23 1002 22 | 6b32b1ac731898894c403f6b621bdda167ab8d7c blob 1645 700 1025 23 | 7147f43ae01c9f04a78d6e80544ed84def06e958 blob 1824 697 1725 24 | 05d3cc770bd3524cc25d47e083d8942ad25033f0 blob 16 28 2422 1 7147f43ae01c9f04a78d6e80544ed84def06e958 25 | c3b8133617bbdb72e237b0f163fade7fbf1f0c18 blob 381 317 2450 2 05d3cc770bd3524cc25d47e083d8942ad25033f0 26 | 8264d7bcc297e15c452a7aef3a2e40934762b7e3 tree 25 38 2767 1 d22fc8a57073fdecae2001d00aff921440d3aabd 27 | 254671773e8cd91e07e36546c9a2d9c27e8dfeec tree 121 115 2805 28 | ba74813270ff557c4a5d1be0562a141bbee4d3e6 blob 16 28 2920 1 6b32b1ac731898894c403f6b621bdda167ab8d7c 29 | b45377f6daf59a4cec9e8de64f5df1533a7994cd blob 10 21 2948 1 7147f43ae01c9f04a78d6e80544ed84def06e958 30 | 9de6c72106b169990a83ce7090c7cad84b6b506b tree 38 49 2969 31 | non delta: 11 objects 32 | chain length = 1: 5 objects 33 | chain length = 2: 1 object 34 | .git/objects/pack/pack-d310969c4ba0ebfe725685fa577a1eec5ecb15b2.pack: ok 35 | 36 | ` 37 | 38 | packFile, err := os.Open(path.Join(RepoDir.Name(), "objects/pack/pack-d310969c4ba0ebfe725685fa577a1eec5ecb15b2.pack")) 39 | if err != nil { 40 | t.Error(err) 41 | return 42 | } 43 | defer packFile.Close() 44 | 45 | idxFile, err := os.Open(path.Join(RepoDir.Name(), "objects/pack/pack-d310969c4ba0ebfe725685fa577a1eec5ecb15b2.idx")) 46 | if err != nil { 47 | t.Error(err) 48 | return 49 | } 50 | defer idxFile.Close() 51 | 52 | type packObjectMock struct { 53 | Name SHA 54 | _type packObjectType 55 | Type string 56 | Size int 57 | SizeInPackfile int 58 | Offset int 59 | Depth int 60 | BaseObjectName SHA 61 | } 62 | objs := map[string]packObjectMock{ 63 | "fe89ee30bbcdfdf376beae530cc53f967012f31c": packObjectMock{Name: SHA("fe89ee30bbcdfdf376beae530cc53f967012f31c"), _type: 1, Type: "commit", Size: 267, SizeInPackfile: 184, Offset: 12}, 64 | "3ead3116d0378089f5ce61086354aac43e736b01": packObjectMock{Name: SHA("3ead3116d0378089f5ce61086354aac43e736b01"), _type: 1, Type: "commit", Size: 243, SizeInPackfile: 170, Offset: 196}, 65 | "1d833eb5b6c5369c0cb7a4a3e20ded237490145f": packObjectMock{Name: SHA("1d833eb5b6c5369c0cb7a4a3e20ded237490145f"), _type: 1, Type: "commit", Size: 262, SizeInPackfile: 180, Offset: 366}, 66 | "a7f92c920ce85f07a33f948aa4fa2548b270024f": packObjectMock{Name: SHA("a7f92c920ce85f07a33f948aa4fa2548b270024f"), _type: 1, Type: "commit", Size: 250, SizeInPackfile: 172, Offset: 546}, 67 | "97eed02ebe122df8fdd853c1215d8775f3d9f1a1": packObjectMock{Name: SHA("97eed02ebe122df8fdd853c1215d8775f3d9f1a1"), _type: 1, Type: "commit", Size: 190, SizeInPackfile: 132, Offset: 718}, 68 | "d22fc8a57073fdecae2001d00aff921440d3aabd": packObjectMock{Name: SHA("d22fc8a57073fdecae2001d00aff921440d3aabd"), _type: 2, Type: "tree", Size: 121, SizeInPackfile: 115, Offset: 850}, 69 | "df891299372c34b57e41cfc50a0113e2afac3210": packObjectMock{Name: SHA("df891299372c34b57e41cfc50a0113e2afac3210"), _type: 2, Type: "tree", Size: 25, SizeInPackfile: 37, Offset: 965, Depth: 1, BaseObjectName: "d22fc8a57073fdecae2001d00aff921440d3aabd"}, 70 | "af6e4fe91a8f9a0f3c03cbec9e1d2aac47345d67": packObjectMock{Name: SHA("af6e4fe91a8f9a0f3c03cbec9e1d2aac47345d67"), _type: 3, Type: "blob", Size: 18, SizeInPackfile: 23, Offset: 1002}, 71 | "6b32b1ac731898894c403f6b621bdda167ab8d7c": packObjectMock{Name: SHA("6b32b1ac731898894c403f6b621bdda167ab8d7c"), _type: 3, Type: "blob", Size: 1645, SizeInPackfile: 700, Offset: 1025}, 72 | "7147f43ae01c9f04a78d6e80544ed84def06e958": packObjectMock{Name: SHA("7147f43ae01c9f04a78d6e80544ed84def06e958"), _type: 3, Type: "blob", Size: 1824, SizeInPackfile: 697, Offset: 1725}, 73 | "05d3cc770bd3524cc25d47e083d8942ad25033f0": packObjectMock{Name: SHA("05d3cc770bd3524cc25d47e083d8942ad25033f0"), _type: 3, Type: "blob", Size: 16, SizeInPackfile: 28, Offset: 2422, Depth: 1, BaseObjectName: "7147f43ae01c9f04a78d6e80544ed84def06e958"}, 74 | "c3b8133617bbdb72e237b0f163fade7fbf1f0c18": packObjectMock{Name: SHA("c3b8133617bbdb72e237b0f163fade7fbf1f0c18"), _type: 3, Type: "blob", Size: 381, SizeInPackfile: 317, Offset: 2450, Depth: 2, BaseObjectName: "05d3cc770bd3524cc25d47e083d8942ad25033f0"}, 75 | "8264d7bcc297e15c452a7aef3a2e40934762b7e3": packObjectMock{Name: SHA("8264d7bcc297e15c452a7aef3a2e40934762b7e3"), _type: 2, Type: "tree", Size: 25, SizeInPackfile: 38, Offset: 2767, Depth: 1, BaseObjectName: "d22fc8a57073fdecae2001d00aff921440d3aabd"}, 76 | "254671773e8cd91e07e36546c9a2d9c27e8dfeec": packObjectMock{Name: SHA("254671773e8cd91e07e36546c9a2d9c27e8dfeec"), _type: 2, Type: "tree", Size: 121, SizeInPackfile: 115, Offset: 2805}, 77 | "ba74813270ff557c4a5d1be0562a141bbee4d3e6": packObjectMock{Name: SHA("ba74813270ff557c4a5d1be0562a141bbee4d3e6"), _type: 3, Type: "blob", Size: 16, SizeInPackfile: 28, Offset: 2920, Depth: 1, BaseObjectName: "6b32b1ac731898894c403f6b621bdda167ab8d7c"}, 78 | "b45377f6daf59a4cec9e8de64f5df1533a7994cd": packObjectMock{Name: SHA("b45377f6daf59a4cec9e8de64f5df1533a7994cd"), _type: 3, Type: "blob", Size: 10, SizeInPackfile: 21, Offset: 2948, Depth: 1, BaseObjectName: "7147f43ae01c9f04a78d6e80544ed84def06e958"}, 79 | "9de6c72106b169990a83ce7090c7cad84b6b506b": packObjectMock{Name: SHA("9de6c72106b169990a83ce7090c7cad84b6b506b"), _type: 2, Type: "tree", Size: 38, SizeInPackfile: 49, Offset: 2969}, 80 | } 81 | 82 | objects, err := VerifyPack(packFile, idxFile) 83 | if err != nil { 84 | t.Error(err) 85 | } 86 | 87 | if len(objects) != len(objs) { 88 | t.Errorf("Read incorrect number of objects: %d, want %d", len(objects), len(objs)) 89 | } 90 | 91 | for _, object := range objects { 92 | expectedObj, ok := objs[string(object.Name)] 93 | if !ok { 94 | t.Errorf("encountered incorrect hash %s", object.Name) 95 | } 96 | if object.err != nil { 97 | log.Printf("%+v", object) 98 | log.Printf("%+v", object.err) 99 | t.Errorf("error reading object %s: %s", object.Name, object.err) 100 | } 101 | if expectedObj._type.String() != object._type.String() && object._type.String() != OBJ_OFS_DELTA.String() { 102 | t.Errorf("expected type %s and received type %s", expectedObj._type, object._type.String()) 103 | } 104 | 105 | if expectedObj.Name != object.Name { 106 | t.Errorf("Expected Name %s and received %s", expectedObj.Name, object.Name) 107 | } 108 | 109 | if expectedObj._type.String() != object.PatchedType().String() { 110 | t.Errorf("Expected _type.String() %s and received %s", expectedObj._type.String(), object.PatchedType().String()) 111 | } 112 | 113 | if expectedObj.Type != object.Type() { 114 | t.Errorf("Expected Type() method to return %s and received %s (%s)", expectedObj.Type, object.Type(), object.Name) 115 | } 116 | 117 | if expectedObj.Size != object.Size { 118 | t.Errorf("Expected Size %d and received %d", expectedObj.Size, object.Size) 119 | } 120 | 121 | if expectedObj.Offset != object.Offset { 122 | t.Errorf("Expected Offset %d and received %d", expectedObj.Offset, object.Offset) 123 | } 124 | } 125 | 126 | /* 127 | if !reflect.DeepEqual(expected, result) { 128 | t.Errorf("Expected and result don't match:\n%+v\n%+v", expected, result) 129 | } 130 | */ 131 | } 132 | 133 | func BenchmarkVerifyPack(b *testing.B) { 134 | packFile, err := os.Open(path.Join(RepoDir.Name(), "objects/pack/pack-d310969c4ba0ebfe725685fa577a1eec5ecb15b2.pack")) 135 | if err != nil { 136 | b.Error(err) 137 | return 138 | } 139 | defer packFile.Close() 140 | 141 | idxFile, err := os.Open(path.Join(RepoDir.Name(), "objects/pack/pack-d310969c4ba0ebfe725685fa577a1eec5ecb15b2.idx")) 142 | if err != nil { 143 | return 144 | } 145 | 146 | defer idxFile.Close() 147 | for i := 0; i < b.N; i++ { 148 | _, err := VerifyPack(packFile, idxFile) 149 | if err != nil { 150 | b.Errorf("error in iteration %d: %s", i, err) 151 | } 152 | packFile.Seek(0, io.SeekStart) 153 | idxFile.Seek(0, io.SeekStart) 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /cat-file_test.go: -------------------------------------------------------------------------------- 1 | package gitgo 2 | 3 | import ( 4 | "compress/zlib" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | "os" 9 | "path/filepath" 10 | "reflect" 11 | "strings" 12 | "testing" 13 | "time" 14 | ) 15 | 16 | func ReadAll(t *testing.T, r io.Reader) []byte { 17 | bts, err := ioutil.ReadAll(r) 18 | if err != nil { 19 | t.Error(err) 20 | } 21 | return bts 22 | } 23 | 24 | // ReaderEqual tests that two readers read the same number of bytes 25 | // and read the same content 26 | func ReaderEqual(t *testing.T, r1 io.Reader, expected io.Reader) (err error) { 27 | bts1, err := ioutil.ReadAll(r1) 28 | if err != nil { 29 | t.Error(err) 30 | return 31 | } 32 | bts2, err := ioutil.ReadAll(expected) 33 | if err != nil { 34 | t.Error(err) 35 | return 36 | } 37 | if !reflect.DeepEqual(bts1, bts2) { 38 | err = fmt.Errorf("Actual does not match expected: %s, %s", string(bts1), string(bts2)) 39 | t.Error(err) 40 | return 41 | } 42 | return nil 43 | } 44 | 45 | func Test_parseObjInitialCommit(t *testing.T) { 46 | tm, err := time.Parse(RFC2822, "Fri Apr 3 11:45:00 2015 -0400") 47 | if err != nil { 48 | t.Fatal(err) 49 | } 50 | const inputSHA = SHA("97eed02ebe122df8fdd853c1215d8775f3d9f1a1") 51 | expected := Commit{ 52 | _type: "commit", 53 | Name: inputSHA, 54 | Tree: "9de6c72106b169990a83ce7090c7cad84b6b506b", 55 | Parents: nil, 56 | Author: "aditya ", 57 | AuthorDate: tm, 58 | Committer: "aditya ", 59 | CommitterDate: tm, 60 | Message: []byte("First commit. Create .gitignore"), 61 | size: "190", 62 | } 63 | const input = "commit 190\x00" + `tree 9de6c72106b169990a83ce7090c7cad84b6b506b 64 | author aditya 1428075900 -0400 65 | committer aditya 1428075900 -0400 66 | 67 | First commit. Create .gitignore` 68 | 69 | pwd, err := os.Open(".") 70 | if err != nil { 71 | t.Fatal(err) 72 | } 73 | 74 | result, err := parseObj(strings.NewReader(input), inputSHA, *pwd) 75 | if err != nil { 76 | t.Error(err) 77 | return 78 | } 79 | 80 | if !reflect.DeepEqual(expected, result) { 81 | t.Errorf("Expected and result don't match:\n%+v\n%+v", expected, result) 82 | } 83 | } 84 | 85 | func Test_parseObjTreeCommit(t *testing.T) { 86 | tm, err := time.Parse(RFC2822, "Mon Apr 6 15:51:36 2015 -0400") 87 | if err != nil { 88 | t.Fatal(err) 89 | } 90 | const inputSHA = SHA("3ead3116d0378089f5ce61086354aac43e736b01") 91 | const fileContents = "commit 243\x00tree d22fc8a57073fdecae2001d00aff921440d3aabd\nparent 1d833eb5b6c5369c0cb7a4a3e20ded237490145f\nauthor aditya 1428349896 -0400\ncommitter aditya 1428349896 -0400\n\nRemove extraneous logging statements\n" 92 | 93 | expected := Commit{ 94 | _type: "commit", 95 | Name: inputSHA, 96 | Tree: "d22fc8a57073fdecae2001d00aff921440d3aabd", 97 | Parents: []SHA{"1d833eb5b6c5369c0cb7a4a3e20ded237490145f"}, 98 | Author: "aditya ", 99 | AuthorDate: tm, 100 | Committer: "aditya ", 101 | CommitterDate: tm, 102 | Message: []byte("Remove extraneous logging statements\n"), 103 | size: "243", 104 | } 105 | 106 | pwd, err := os.Open(".") 107 | if err != nil { 108 | t.Fatal(err) 109 | } 110 | 111 | result, err := parseObj(strings.NewReader(fileContents), inputSHA, *pwd) 112 | if err != nil { 113 | t.Error(err) 114 | return 115 | } 116 | 117 | if !reflect.DeepEqual(expected, result) { 118 | t.Errorf("Expected and result don't match:\n%+v\n%+v", expected, result) 119 | } 120 | } 121 | 122 | func Test_ParseTree(t *testing.T) { 123 | const inputSha = SHA("1efecd717188441397c07f267cf468fdf04d4796") 124 | expected := Tree{ 125 | _type: "tree", 126 | size: "156", 127 | Blobs: []objectMeta{ 128 | objectMeta{SHA("af6e4fe91a8f9a0f3c03cbec9e1d2aac47345d67"), "100644", ".gitignore"}, 129 | objectMeta{SHA("f45d37d9add8f21eb84678f6d2c66377c4dd0c5e"), "100644", "cat-file.go"}, 130 | objectMeta{SHA("2c225b962d6666011c69ca5c2c67204959f8ba32"), "100644", "cat-file_test.go"}, 131 | }, 132 | Trees: []objectMeta{ 133 | objectMeta{SHA("d564d0bc3dd917926892c55e3706cc116d5b165e"), "040000", "examples"}, 134 | }, 135 | } 136 | result, err := NewObject(inputSha, *RepoDir) 137 | if err != nil { 138 | t.Error(err) 139 | } 140 | if !reflect.DeepEqual(expected, result) { 141 | t.Errorf("Expected and result don't match:\n\n%+v\n\n%+v", expected, result) 142 | } 143 | 144 | } 145 | 146 | func Test_ParseBlob(t *testing.T) { 147 | const inputSha = SHA("af6e4fe91a8f9a0f3c03cbec9e1d2aac47345d67") 148 | expected := Blob{ 149 | _type: "blob", 150 | size: "18", 151 | Contents: []byte("*.swp\n*.swo\n*.swn\n"), 152 | } 153 | result, err := NewObject(inputSha, *RepoDir) 154 | if err != nil { 155 | t.Error(err) 156 | } 157 | if !reflect.DeepEqual(expected, result) { 158 | t.Errorf("Expected and result don't match:\n\n%+v\n\n%+v", expected, result) 159 | } 160 | 161 | } 162 | 163 | func Test_ParsePackfile(t *testing.T) { 164 | const inputSha = SHA("c3b8133617bbdb72e237b0f163fade7fbf1f0c18") 165 | const expected = 2160 166 | 167 | result, err := NewObject(inputSha, *RepoDir) 168 | if err != nil { 169 | t.Error(err) 170 | return 171 | } 172 | 173 | // TODO remove this type assertion 174 | if len(result.(Blob).rawData) != expected { 175 | t.Errorf("expected and result don't match") 176 | } 177 | } 178 | 179 | func Test_ParsePrefix(t *testing.T) { 180 | const inputSha = SHA("1efecd717188441397c07f267cf468fdf04d4796") 181 | expected := Tree{ 182 | _type: "tree", 183 | size: "156", 184 | Blobs: []objectMeta{ 185 | objectMeta{SHA("af6e4fe91a8f9a0f3c03cbec9e1d2aac47345d67"), "100644", ".gitignore"}, 186 | objectMeta{SHA("f45d37d9add8f21eb84678f6d2c66377c4dd0c5e"), "100644", "cat-file.go"}, 187 | objectMeta{SHA("2c225b962d6666011c69ca5c2c67204959f8ba32"), "100644", "cat-file_test.go"}, 188 | }, 189 | Trees: []objectMeta{ 190 | objectMeta{SHA("d564d0bc3dd917926892c55e3706cc116d5b165e"), "040000", "examples"}, 191 | }, 192 | } 193 | result, err := NewObject(inputSha[:15], *RepoDir) 194 | if err != nil { 195 | t.Error(err) 196 | return 197 | } 198 | if !reflect.DeepEqual(expected, result) { 199 | t.Errorf("Expected and result don't match:\n\n%+v\n\n%+v", expected, result) 200 | } 201 | } 202 | 203 | func Test_ParsePrefixPackfile(t *testing.T) { 204 | // in the test directory, this is stored in a packfile 205 | const inputSHA SHA = "b45377f6daf59a4cec9e8de64f5df1533a7994cd" 206 | _, err := NewObject(inputSHA[:15], *RepoDir) 207 | if err != nil { 208 | t.Error(err) 209 | } 210 | } 211 | 212 | func Test_NakedDirectory(t *testing.T) { 213 | // Test that we can read a "naked" directory 214 | // (ie, without the .git extension) 215 | 216 | dirName := filepath.Clean(filepath.Join(RepoDir.Name(), "..")) 217 | dir, err := os.Open(dirName) 218 | if err != nil { 219 | t.Fatal(err) 220 | } 221 | const inputSHA SHA = "254671773e8cd91e07e36546c9a2d9c27e8dfeec" 222 | _, err = NewObject(inputSHA[:15], *dir) 223 | if err != nil { 224 | t.Error(err) 225 | } 226 | } 227 | 228 | func Test_Subdirectory(t *testing.T) { 229 | // Test that we can read a subdirectory of a git repo 230 | 231 | dirName := filepath.Clean(filepath.Join(RepoDir.Name(), "..", "subdir")) 232 | dir, err := os.Open(dirName) 233 | if err != nil { 234 | t.Fatal(err) 235 | } 236 | const inputSHA SHA = "254671773e8cd91e07e36546c9a2d9c27e8dfeec" 237 | _, err = NewObject(inputSHA[:15], *dir) 238 | if err != nil { 239 | t.Error(err) 240 | } 241 | } 242 | 243 | // BenchmarkCatFile benchmarks the entire CatFile operation, 244 | // including opening the current directory and searching for the location 245 | // of the closest parent git repository 246 | func BenchmarkCatFile(b *testing.B) { 247 | const inputSha = SHA("af6e4fe91a8f9a0f3c03cbec9e1d2aac47345d67") 248 | for i := 0; i < b.N; i++ { 249 | _, _ = CatFile(inputSha) //, *RepoDir) 250 | } 251 | } 252 | 253 | // BenchmarkParseObject benchmarks the operation that reads a compressed object file 254 | // and parses it into a blob GitObject. The benchmark includes the zlib inflate operation. 255 | // It uses an object that is not stored within a packfile. 256 | func BenchmarkParseObject(b *testing.B) { 257 | const inputSha = SHA("af6e4fe91a8f9a0f3c03cbec9e1d2aac47345d67") 258 | basedir, err := os.Open(filepath.Join("test_data", ".git")) 259 | if err != nil { 260 | b.Error(err) 261 | } 262 | defer basedir.Close() 263 | f, err := os.Open(filepath.Join("test_data", ".git", "objects", "af", "6e4fe91a8f9a0f3c03cbec9e1d2aac47345d67")) 264 | if err != nil { 265 | b.Error(err) 266 | } 267 | defer f.Close() 268 | 269 | for i := 0; i < b.N; i++ { 270 | f.Seek(0, io.SeekStart) 271 | r, err := zlib.NewReader(f) 272 | if err != nil { 273 | b.Fatalf("error on iteration %d: %s", i, err) 274 | } 275 | obj, err := parseObj(r, inputSha, *basedir) 276 | if err != nil { 277 | b.Fatalf("error on iteration %d: %s", i, err) 278 | } 279 | blb := obj.(Blob) 280 | if blb.size != "18" { 281 | b.Errorf("Expected size 18 and found size %d", blb.size) 282 | } 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /verify-pack.go: -------------------------------------------------------------------------------- 1 | package gitgo 2 | 3 | import ( 4 | "compress/zlib" 5 | "fmt" 6 | "io" 7 | "os" 8 | "reflect" 9 | ) 10 | 11 | type errReadSeeker struct { 12 | r io.ReadSeeker 13 | err error 14 | } 15 | 16 | // Read, but only if no errors have been encountered 17 | // in a previous read (including io.EOF) 18 | func (er *errReadSeeker) read(buf []byte) int { 19 | var n int 20 | if er.err != nil { 21 | return 0 22 | } 23 | n, er.err = io.ReadFull(er.r, buf) 24 | return n 25 | } 26 | 27 | func (er *errReadSeeker) Seek(offset int64, whence int) (int64, error) { 28 | return er.r.Seek(offset, whence) 29 | } 30 | 31 | // VerifyPack returns the pack objects contained in the packfile and 32 | // corresponding index file. 33 | func VerifyPack(pack io.ReadSeeker, idx io.Reader) ([]*packObject, error) { 34 | 35 | objectsMap := map[SHA]*packObject{} 36 | objects, err := parsePack(errReadSeeker{pack, nil}, idx) 37 | for _, object := range objects { 38 | objectsMap[object.Name] = object 39 | } 40 | 41 | for _, object := range objectsMap { 42 | if object.err != nil { 43 | continue 44 | } 45 | if object._type == OBJ_OFS_DELTA { 46 | // TODO improve this 47 | // linear search to find the right offset 48 | var base *packObject 49 | for _, o := range objects { 50 | if o.Offset == object.Offset-object.negativeOffset { 51 | base = o 52 | break 53 | } 54 | } 55 | if base == nil { 56 | object.err = fmt.Errorf("could not find object with negative offset %d - %d for %s", object.Offset, object.negativeOffset, object.Name) 57 | continue 58 | } 59 | object.BaseObjectName = base.Name 60 | } 61 | } 62 | 63 | for _, object := range objectsMap { 64 | 65 | object.err = object.Patch(objectsMap) 66 | } 67 | return objects, err 68 | } 69 | 70 | func parsePack(pack errReadSeeker, idx io.Reader) (objects []*packObject, err error) { 71 | signature := make([]byte, 4) 72 | pack.read(signature) 73 | if string(signature) != "PACK" { 74 | return nil, fmt.Errorf("Received invalid signature: %s", string(signature)) 75 | } 76 | if err != nil { 77 | return nil, err 78 | } 79 | version := make([]byte, 4) 80 | pack.read(version) 81 | 82 | // TODO use encoding/binary here 83 | v := version[3] 84 | switch v { 85 | case 2: 86 | // Parse version 2 packfile 87 | objects, err = parseIdx(idx, 2) 88 | if err != nil { 89 | return 90 | } 91 | objects, err = parsePackV2(pack, objects) 92 | return 93 | 94 | default: 95 | return nil, fmt.Errorf("cannot parse packfile with version %d", v) 96 | } 97 | } 98 | 99 | func bytesToNum(b []byte) uint { 100 | var n uint 101 | for i := 0; i < len(b); i++ { 102 | n = n | (uint(b[len(b)-i-1]) << uint(i*8)) 103 | } 104 | return n 105 | } 106 | 107 | // parsePackV2 parses a packfile that uses 108 | // version 2 of the format 109 | func parsePackV2(r errReadSeeker, objects []*packObject) ([]*packObject, error) { 110 | 111 | numObjectsBts := make([]byte, 4) 112 | r.read(numObjectsBts) 113 | if int(bytesToNum(numObjectsBts)) != len(objects) { 114 | return nil, fmt.Errorf("Expected %d objects and found %d", len(objects), numObjectsBts) 115 | } 116 | 117 | for _, object := range objects { 118 | 119 | var btsread int 120 | r.Seek(int64(object.Offset), os.SEEK_SET) 121 | _bytes := make([]byte, 1) 122 | btsread += r.read(_bytes) 123 | _byte := _bytes[0] 124 | 125 | // This will extract the last three bits of 126 | // the first nibble in the byte 127 | // which tells us the object type 128 | object._type = packObjectType(((_byte >> 4) & 7)) 129 | 130 | // determine the (decompressed) object size 131 | // and then deflate the following bytes 132 | 133 | // The most-significant byte (MSB) 134 | // tells us whether we need to read more bytes 135 | // to get the encoded object size 136 | MSB := (_byte & 128) // will be either 128 or 0 137 | 138 | // This will extract the last four bits of the byte 139 | var objectSize = int((uint(_byte) & 15)) 140 | 141 | // shift the first size by 0 142 | // and the rest by 4 + (i-1) * 7 143 | var shift uint = 4 144 | 145 | // If the most-significant bit is 0, this is the last byte 146 | // for the object size 147 | for MSB > 0 { 148 | // Keep reading the size until the MSB is 0 149 | _bytes := make([]byte, 1) 150 | btsread += r.read(_bytes) 151 | _byte := _bytes[0] 152 | 153 | MSB = (_byte & 128) 154 | 155 | objectSize += int((uint(_byte) & 127) << shift) 156 | shift += 7 157 | } 158 | 159 | object.Size = objectSize 160 | switch { 161 | case object._type < 5: 162 | // the object is a commit, tree, blob, or tag 163 | 164 | // (objectSize) is the size, in bytes, of this object *when expanded* 165 | // the IDX file tells us how many *compressed* bytes the object will take 166 | // (in other words, how much space to allocate for the result) 167 | object.Data = make([]byte, objectSize) 168 | 169 | zr, err := zlib.NewReader(r.r) 170 | if err != nil { 171 | return nil, err 172 | } 173 | 174 | n, err := zr.Read(object.Data) 175 | if err != nil { 176 | if err == io.EOF { 177 | err = nil 178 | } else { 179 | return nil, err 180 | } 181 | } 182 | zr.Close() 183 | object.Data = object.Data[:n] 184 | 185 | // TODO figure out why sometimes n < objectSize 186 | 187 | case object._type == OBJ_OFS_DELTA: 188 | // read the n-byte offset 189 | // from the git docs: 190 | // "n bytes with MSB set in all but the last one. 191 | // The offset is then the number constructed by 192 | // concatenating the lower 7 bit of each byte, and 193 | // for n >= 2 adding 2^7 + 2^14 + ... + 2^(7*(n-1)) 194 | // to the result." 195 | 196 | var offset int 197 | 198 | // number of bytes read in variable length encoding 199 | var nbytes uint 200 | 201 | MSB := 128 202 | for (MSB & 128) > 0 { 203 | nbytes++ 204 | 205 | // Keep reading the size until the MSB is 0 206 | _bytes := make([]byte, 1) 207 | r.read(_bytes) 208 | _byte := _bytes[0] 209 | 210 | sevenBytes := uint(_byte) & 127 211 | 212 | offset = (offset << 7) + int(sevenBytes) 213 | 214 | MSB = int(_byte & 128) 215 | if MSB == 0 { 216 | break 217 | } 218 | } 219 | 220 | if nbytes >= 2 { 221 | offset += (1 << (7 * (nbytes - 1))) 222 | } 223 | 224 | object.negativeOffset = offset 225 | object.baseOffset = object.Offset - object.negativeOffset 226 | object.Data = make([]byte, objectSize) 227 | 228 | zr, err := zlib.NewReader(r.r) 229 | if err != nil { 230 | object.err = err 231 | continue 232 | } 233 | n, err := zr.Read(object.Data) 234 | if err != nil && err != io.EOF { 235 | object.err = err 236 | continue 237 | } 238 | object.Data = object.Data[:n] 239 | zr.Close() 240 | if len(object.Data) != objectSize { 241 | object.err = fmt.Errorf("received wrong object size: %d (expected %d)", object.Data, objectSize) 242 | } 243 | 244 | case object._type == OBJ_REF_DELTA: 245 | r.Seek(int64(object.Offset), os.SEEK_SET) 246 | // Read the 20-byte base object name 247 | baseObjName := make([]byte, 20) 248 | 249 | r.read(baseObjName) 250 | object.Data = make([]byte, objectSize) 251 | 252 | zr, err := zlib.NewReader(r.r) 253 | if err != nil { 254 | object.err = err 255 | continue 256 | } 257 | n, err := zr.Read(object.Data) 258 | if err != nil && err != io.EOF { 259 | object.err = err 260 | continue 261 | } 262 | object.Data = object.Data[:n] 263 | zr.Close() 264 | } 265 | } 266 | 267 | return objects, r.err 268 | } 269 | 270 | func parseIdx(idx io.Reader, version int) (objects []*packObject, err error) { 271 | if version != 2 { 272 | return nil, fmt.Errorf("cannot parse IDX with version %d", version) 273 | } 274 | // parse version 2 idxfile 275 | 276 | // Version 2 starts with a 4-byte magic number 277 | header := make([]byte, 4) 278 | n, err := idx.Read(header) 279 | if err != nil { 280 | return nil, err 281 | } 282 | 283 | if !reflect.DeepEqual([]byte{255, 116, 79, 99}, header) { 284 | return nil, fmt.Errorf("invalid IDX header: %q", string(header)) 285 | } 286 | 287 | // Then the version number in four bytes 288 | versionBts := make([]byte, 4) 289 | _, err = idx.Read(versionBts) 290 | if err != nil { 291 | return nil, err 292 | } 293 | // We already know the version, so we can ignore it 294 | 295 | // Then the fanout table 296 | // The fanout table has 256 entries, each 4 bytes long 297 | fanoutTableFlat := make([]byte, 256*4) 298 | n, err = idx.Read(fanoutTableFlat) 299 | if err == nil && n != len(fanoutTableFlat) { 300 | err = fmt.Errorf("read incomplete fanout table: %d", n) 301 | } 302 | if err != nil { 303 | return nil, err 304 | } 305 | 306 | // Initialize the flat fanout table 307 | fanoutTable := make([][]byte, 256) 308 | for i := 0; i < len(fanoutTableFlat); i += 4 { 309 | entry := fanoutTableFlat[i : i+4] 310 | fanoutTable[(i+1)/4] = entry 311 | } 312 | 313 | numObjects := int(bytesToNum(fanoutTable[len(fanoutTable)-1])) 314 | objects = make([]*packObject, numObjects) 315 | 316 | objectNames := make([]SHA, numObjects) 317 | 318 | for i := 0; i < numObjects; i++ { 319 | sha := make([]byte, 20) 320 | n, err = idx.Read(sha) 321 | if err != nil { 322 | return nil, err 323 | } 324 | 325 | objectNames[i] = SHA(fmt.Sprintf("%x", sha[:n])) 326 | objects[i] = &packObject{Name: SHA(fmt.Sprintf("%x", sha[:n]))} 327 | } 328 | 329 | // Then come 4-byte CRC32 values 330 | crc32Table := make([]byte, numObjects*4) 331 | _, err = idx.Read(crc32Table) 332 | if err != nil { 333 | return nil, err 334 | } 335 | 336 | // Next come 4-byte offset values 337 | // If the MSB is set, there is an index into the next table 338 | // otherwise, these are 31 bits each 339 | offsetsFlat := make([]byte, numObjects*4) 340 | _, err = idx.Read(offsetsFlat) 341 | if err != nil { 342 | return nil, err 343 | } 344 | 345 | offsets := make([]int, numObjects) 346 | for i := 0; i < len(offsets); i++ { 347 | offset := int(bytesToNum(offsetsFlat[i*4 : (i+1)*4])) 348 | // check if the MSB is 1 349 | if offset&2147483648 > 0 { 350 | return nil, fmt.Errorf("packfile is too large to parse") 351 | } 352 | offsets[i] = offset 353 | objects[i].Offset = offset 354 | } 355 | 356 | // If the pack file is more than 2 GB, there will be a table of 8-byte offset entries here 357 | // TODO implement this 358 | 359 | // This is the same as the checksum at the end of the corresponding packfile 360 | packfileChecksum := make([]byte, 20) 361 | _, err = idx.Read(packfileChecksum) 362 | if err != nil { 363 | return 364 | } 365 | 366 | // This is the checksum of all of the above data 367 | // We're not checking it now, but if we can't read it properly 368 | // that means an error has occurred earlier in parsing 369 | idxChecksum := make([]byte, 20) 370 | _, err = idx.Read(idxChecksum) 371 | if err != nil { 372 | return 373 | } 374 | 375 | // TODO check that there isn't any data left 376 | 377 | return objects, err 378 | } 379 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Aditya Mukerjee 2 | 3 | All files in this project are provided under the following terms, except the files `test-delta.c`, `test-delta-new.c, `test-delta-new.delta`, `zlib.c`, `zlib-changed.c`, and `zlib-delta`. The license for those files can be found in the `test_data` directory. 4 | 5 | Apache License 6 | Version 2.0, January 2004 7 | http://www.apache.org/licenses/ 8 | 9 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 10 | 11 | 1. Definitions. 12 | 13 | "License" shall mean the terms and conditions for use, reproduction, 14 | and distribution as defined by Sections 1 through 9 of this document. 15 | 16 | "Licensor" shall mean the copyright owner or entity authorized by 17 | the copyright owner that is granting the License. 18 | 19 | "Legal Entity" shall mean the union of the acting entity and all 20 | other entities that control, are controlled by, or are under common 21 | control with that entity. For the purposes of this definition, 22 | "control" means (i) the power, direct or indirect, to cause the 23 | direction or management of such entity, whether by contract or 24 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 25 | outstanding shares, or (iii) beneficial ownership of such entity. 26 | 27 | "You" (or "Your") shall mean an individual or Legal Entity 28 | exercising permissions granted by this License. 29 | 30 | "Source" form shall mean the preferred form for making modifications, 31 | including but not limited to software source code, documentation 32 | source, and configuration files. 33 | 34 | "Object" form shall mean any form resulting from mechanical 35 | transformation or translation of a Source form, including but 36 | not limited to compiled object code, generated documentation, 37 | and conversions to other media types. 38 | 39 | "Work" shall mean the work of authorship, whether in Source or 40 | Object form, made available under the License, as indicated by a 41 | copyright notice that is included in or attached to the work 42 | (an example is provided in the Appendix below). 43 | 44 | "Derivative Works" shall mean any work, whether in Source or Object 45 | form, that is based on (or derived from) the Work and for which the 46 | editorial revisions, annotations, elaborations, or other modifications 47 | represent, as a whole, an original work of authorship. For the purposes 48 | of this License, Derivative Works shall not include works that remain 49 | separable from, or merely link (or bind by name) to the interfaces of, 50 | the Work and Derivative Works thereof. 51 | 52 | "Contribution" shall mean any work of authorship, including 53 | the original version of the Work and any modifications or additions 54 | to that Work or Derivative Works thereof, that is intentionally 55 | submitted to Licensor for inclusion in the Work by the copyright owner 56 | or by an individual or Legal Entity authorized to submit on behalf of 57 | the copyright owner. For the purposes of this definition, "submitted" 58 | means any form of electronic, verbal, or written communication sent 59 | to the Licensor or its representatives, including but not limited to 60 | communication on electronic mailing lists, source code control systems, 61 | and issue tracking systems that are managed by, or on behalf of, the 62 | Licensor for the purpose of discussing and improving the Work, but 63 | excluding communication that is conspicuously marked or otherwise 64 | designated in writing by the copyright owner as "Not a Contribution." 65 | 66 | "Contributor" shall mean Licensor and any individual or Legal Entity 67 | on behalf of whom a Contribution has been received by Licensor and 68 | subsequently incorporated within the Work. 69 | 70 | 2. Grant of Copyright License. Subject to the terms and conditions of 71 | this License, each Contributor hereby grants to You a perpetual, 72 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 73 | copyright license to reproduce, prepare Derivative Works of, 74 | publicly display, publicly perform, sublicense, and distribute the 75 | Work and such Derivative Works in Source or Object form. 76 | 77 | 3. Grant of Patent License. Subject to the terms and conditions of 78 | this License, each Contributor hereby grants to You a perpetual, 79 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 80 | (except as stated in this section) patent license to make, have made, 81 | use, offer to sell, sell, import, and otherwise transfer the Work, 82 | where such license applies only to those patent claims licensable 83 | by such Contributor that are necessarily infringed by their 84 | Contribution(s) alone or by combination of their Contribution(s) 85 | with the Work to which such Contribution(s) was submitted. If You 86 | institute patent litigation against any entity (including a 87 | cross-claim or counterclaim in a lawsuit) alleging that the Work 88 | or a Contribution incorporated within the Work constitutes direct 89 | or contributory patent infringement, then any patent licenses 90 | granted to You under this License for that Work shall terminate 91 | as of the date such litigation is filed. 92 | 93 | 4. Redistribution. You may reproduce and distribute copies of the 94 | Work or Derivative Works thereof in any medium, with or without 95 | modifications, and in Source or Object form, provided that You 96 | meet the following conditions: 97 | 98 | (a) You must give any other recipients of the Work or 99 | Derivative Works a copy of this License; and 100 | 101 | (b) You must cause any modified files to carry prominent notices 102 | stating that You changed the files; and 103 | 104 | (c) You must retain, in the Source form of any Derivative Works 105 | that You distribute, all copyright, patent, trademark, and 106 | attribution notices from the Source form of the Work, 107 | excluding those notices that do not pertain to any part of 108 | the Derivative Works; and 109 | 110 | (d) If the Work includes a "NOTICE" text file as part of its 111 | distribution, then any Derivative Works that You distribute must 112 | include a readable copy of the attribution notices contained 113 | within such NOTICE file, excluding those notices that do not 114 | pertain to any part of the Derivative Works, in at least one 115 | of the following places: within a NOTICE text file distributed 116 | as part of the Derivative Works; within the Source form or 117 | documentation, if provided along with the Derivative Works; or, 118 | within a display generated by the Derivative Works, if and 119 | wherever such third-party notices normally appear. The contents 120 | of the NOTICE file are for informational purposes only and 121 | do not modify the License. You may add Your own attribution 122 | notices within Derivative Works that You distribute, alongside 123 | or as an addendum to the NOTICE text from the Work, provided 124 | that such additional attribution notices cannot be construed 125 | as modifying the License. 126 | 127 | You may add Your own copyright statement to Your modifications and 128 | may provide additional or different license terms and conditions 129 | for use, reproduction, or distribution of Your modifications, or 130 | for any such Derivative Works as a whole, provided Your use, 131 | reproduction, and distribution of the Work otherwise complies with 132 | the conditions stated in this License. 133 | 134 | 5. Submission of Contributions. Unless You explicitly state otherwise, 135 | any Contribution intentionally submitted for inclusion in the Work 136 | by You to the Licensor shall be under the terms and conditions of 137 | this License, without any additional terms or conditions. 138 | Notwithstanding the above, nothing herein shall supersede or modify 139 | the terms of any separate license agreement you may have executed 140 | with Licensor regarding such Contributions. 141 | 142 | 6. Trademarks. This License does not grant permission to use the trade 143 | names, trademarks, service marks, or product names of the Licensor, 144 | except as required for reasonable and customary use in describing the 145 | origin of the Work and reproducing the content of the NOTICE file. 146 | 147 | 7. Disclaimer of Warranty. Unless required by applicable law or 148 | agreed to in writing, Licensor provides the Work (and each 149 | Contributor provides its Contributions) on an "AS IS" BASIS, 150 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 151 | implied, including, without limitation, any warranties or conditions 152 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 153 | PARTICULAR PURPOSE. You are solely responsible for determining the 154 | appropriateness of using or redistributing the Work and assume any 155 | risks associated with Your exercise of permissions under this License. 156 | 157 | 8. Limitation of Liability. In no event and under no legal theory, 158 | whether in tort (including negligence), contract, or otherwise, 159 | unless required by applicable law (such as deliberate and grossly 160 | negligent acts) or agreed to in writing, shall any Contributor be 161 | liable to You for damages, including any direct, indirect, special, 162 | incidental, or consequential damages of any character arising as a 163 | result of this License or out of the use or inability to use the 164 | Work (including but not limited to damages for loss of goodwill, 165 | work stoppage, computer failure or malfunction, or any and all 166 | other commercial damages or losses), even if such Contributor 167 | has been advised of the possibility of such damages. 168 | 169 | 9. Accepting Warranty or Additional Liability. While redistributing 170 | the Work or Derivative Works thereof, You may choose to offer, 171 | and charge a fee for, acceptance of support, warranty, indemnity, 172 | or other liability obligations and/or rights consistent with this 173 | License. However, in accepting such obligations, You may act only 174 | on Your own behalf and on Your sole responsibility, not on behalf 175 | of any other Contributor, and only if You agree to indemnify, 176 | defend, and hold each Contributor harmless for any liability 177 | incurred by, or claims asserted against, such Contributor by reason 178 | of your accepting any such warranty or additional liability. 179 | 180 | END OF TERMS AND CONDITIONS 181 | 182 | APPENDIX: How to apply the Apache License to your work. 183 | 184 | To apply the Apache License to your work, attach the following 185 | boilerplate notice, with the fields enclosed by brackets "[]" 186 | replaced with your own identifying information. (Don't include 187 | the brackets!) The text should be enclosed in the appropriate 188 | comment syntax for the file format. We also recommend that a 189 | file or class name and description of purpose be included on the 190 | same "printed page" as the copyright notice for easier 191 | identification within third-party archives. 192 | 193 | Copyright [yyyy] [name of copyright owner] 194 | 195 | Licensed under the Apache License, Version 2.0 (the "License"); 196 | you may not use this file except in compliance with the License. 197 | You may obtain a copy of the License at 198 | 199 | http://www.apache.org/licenses/LICENSE-2.0 200 | 201 | Unless required by applicable law or agreed to in writing, software 202 | distributed under the License is distributed on an "AS IS" BASIS, 203 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 204 | See the License for the specific language governing permissions and 205 | limitations under the License. 206 | -------------------------------------------------------------------------------- /object.go: -------------------------------------------------------------------------------- 1 | package gitgo 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "compress/zlib" 7 | "encoding/hex" 8 | "fmt" 9 | "io" 10 | "io/ioutil" 11 | "os" 12 | "path/filepath" 13 | "strconv" 14 | "strings" 15 | "time" 16 | ) 17 | 18 | const ( 19 | RFC2822 = "Mon Jan 2 15:04:05 2006 -0700" 20 | ) 21 | 22 | // GitObject represents a commit, tree, or blob. 23 | // Under the hood, these may be objects stored directly 24 | // or through packfiles 25 | type GitObject interface { 26 | Type() string 27 | //Contents() string 28 | } 29 | 30 | type gitObject struct { 31 | Type string 32 | 33 | // Commit fields 34 | Tree string 35 | Parents []string 36 | Author string 37 | Committer string 38 | Message []byte 39 | Size string 40 | 41 | // Tree 42 | Blobs []objectMeta 43 | Trees []objectMeta 44 | 45 | // Blob 46 | Contents []byte 47 | } 48 | 49 | // A Blob compresses content from a file 50 | type Blob struct { 51 | _type string 52 | size string 53 | Contents []byte 54 | rawData []byte 55 | } 56 | 57 | func (b Blob) Type() string { 58 | return b._type 59 | } 60 | 61 | type Commit struct { 62 | _type string 63 | Name SHA 64 | Tree string 65 | Parents []SHA 66 | Author string 67 | AuthorDate time.Time 68 | Committer string 69 | CommitterDate time.Time 70 | Message []byte 71 | size string 72 | rawData []byte 73 | } 74 | 75 | func (c Commit) Type() string { 76 | return c._type 77 | } 78 | 79 | type Tree struct { 80 | _type string 81 | Blobs []objectMeta 82 | Trees []objectMeta 83 | size string 84 | } 85 | 86 | func (t Tree) Type() string { 87 | return t._type 88 | } 89 | 90 | // objectMeta contains the metadata 91 | // (hash, permissions, and filename) 92 | // corresponding either to a blob (leaf) or another tree 93 | type objectMeta struct { 94 | Hash SHA 95 | Perms string 96 | filename string 97 | } 98 | 99 | func NewObject(input SHA, basedir os.File) (obj GitObject, err error) { 100 | repo := Repository{Basedir: basedir} 101 | return repo.Object(input) 102 | } 103 | 104 | func newObject(input SHA, basedir *os.File, packfiles []*packfile) (obj GitObject, err error) { 105 | 106 | if filepath.Base(basedir.Name()) != ".git" { 107 | defer basedir.Close() 108 | basedir, err = os.Open(filepath.Join(basedir.Name(), ".git")) 109 | if err != nil { 110 | return nil, err 111 | } 112 | } 113 | 114 | candidateName := basedir.Name() 115 | for { 116 | candidate, err := os.Open(candidateName) 117 | if err == nil { 118 | basedir = candidate 119 | break 120 | } 121 | if !os.IsNotExist(err) { 122 | return nil, err 123 | } 124 | 125 | // This should not be the main condition of the for loop 126 | // just in case the filesystem root directory contains 127 | // a .git subdirectory 128 | // TODO check for mountpoint 129 | if candidateName == "/" { 130 | return nil, fmt.Errorf("not a git repository (or any parent up to root /") 131 | } 132 | candidateName = filepath.Join(candidate.Name(), "..", "..", ".git") 133 | } 134 | 135 | if len(input) < 4 { 136 | return nil, fmt.Errorf("input SHA must be at least 4 characters") 137 | } 138 | 139 | filename := filepath.Join(basedir.Name(), "objects", string(input[:2]), string(input[2:])) 140 | _, err = os.Stat(filename) 141 | if err != nil { 142 | if !os.IsNotExist(err) { 143 | return nil, err 144 | } 145 | 146 | // check the directory for a file with the SHA as a prefix 147 | _, err = os.Stat(filepath.Join(basedir.Name(), "objects", string(input[:2]))) 148 | if err != nil { 149 | if !os.IsNotExist(err) { 150 | return nil, err 151 | } 152 | } else { 153 | dirname := filepath.Join(basedir.Name(), "objects", string(input[:2])) 154 | files, err := ioutil.ReadDir(dirname) 155 | if err != nil { 156 | return nil, err 157 | } 158 | for _, file := range files { 159 | if strings.HasPrefix(file.Name(), string(input[2:])) { 160 | return objectFromFile(filepath.Join(dirname, file.Name()), input, *basedir) 161 | } 162 | } 163 | } 164 | 165 | // try the packfile 166 | for _, pack := range packfiles { 167 | if p, ok := pack.objects[input]; ok { 168 | return p.normalize(*basedir) 169 | } 170 | for _, object := range pack.objects { 171 | if strings.HasPrefix(string(object.Name), string(input)) { 172 | return object.normalize(*basedir) 173 | } 174 | } 175 | } 176 | return nil, fmt.Errorf("object not in any packfile: %s", input) 177 | } 178 | 179 | f, err := os.Open(filename) 180 | if err != nil { 181 | return nil, err 182 | } 183 | defer f.Close() 184 | r, err := zlib.NewReader(f) 185 | if err != nil { 186 | return nil, err 187 | } 188 | return parseObj(r, input, *basedir) 189 | 190 | } 191 | 192 | func objectFromFile(filename string, name SHA, basedir os.File) (GitObject, error) { 193 | f, err := os.Open(filename) 194 | if err != nil { 195 | return nil, err 196 | } 197 | defer f.Close() 198 | r, err := zlib.NewReader(f) 199 | if err != nil { 200 | return nil, err 201 | } 202 | return parseObj(r, name, basedir) 203 | } 204 | 205 | func normalizePerms(perms string) string { 206 | // TODO don't store permissions as a string 207 | for len(perms) < 6 { 208 | perms = "0" + perms 209 | } 210 | return perms 211 | } 212 | 213 | func parseObj(r io.Reader, name SHA, basedir os.File) (result GitObject, err error) { 214 | 215 | var resultType string 216 | var resultSize string 217 | scnr := scanner{r, nil, nil} 218 | for scnr.scan() { 219 | txt := string(scnr.data) 220 | if txt == " " { 221 | break 222 | } 223 | resultType += txt 224 | } 225 | 226 | for scnr.scan() { 227 | txt := string(scnr.data) 228 | if txt == "\x00" { 229 | break 230 | } 231 | resultSize += txt 232 | } 233 | 234 | if scnr.Err() != nil { 235 | return nil, scnr.Err() 236 | } 237 | 238 | switch resultType { 239 | case "commit": 240 | return parseCommit(r, resultSize, name) 241 | case "tree": 242 | return parseTree(r, resultSize, basedir) 243 | case "blob": 244 | return parseBlob(r, resultSize) 245 | default: 246 | err = fmt.Errorf("Received unknown object type %s", resultType) 247 | } 248 | 249 | return 250 | } 251 | 252 | func parseCommit(r io.Reader, resultSize string, name SHA) (Commit, error) { 253 | var commit = Commit{_type: "commit", size: resultSize} 254 | 255 | scnr := bufio.NewScanner(r) 256 | scnr.Split(ScanLinesNoTrim) 257 | 258 | var commitMessageLines [][]byte 259 | for scnr.Scan() { 260 | line := scnr.Bytes() 261 | trimmedLine := bytes.TrimRight(line, "\r\n") 262 | if commitMessageLines == nil && len(bytes.Fields(trimmedLine)) == 0 { 263 | // Everything after the first empty line is the commit message 264 | commitMessageLines = [][]byte{} 265 | continue 266 | } 267 | 268 | if commitMessageLines != nil { 269 | // We have already seen an empty line 270 | commitMessageLines = append(commitMessageLines, line) 271 | continue 272 | } 273 | 274 | parts := bytes.Fields(trimmedLine) 275 | key := parts[0] 276 | switch keyType(key) { 277 | case treeKey: 278 | commit.Tree = string(parts[1]) 279 | case parentKey: 280 | commit.Parents = append(commit.Parents, SHA(string(parts[1]))) 281 | case authorKey: 282 | authorline := string(bytes.Join(parts[1:], []byte(" "))) 283 | author, date, err := parseAuthorString(authorline) 284 | if err != nil { 285 | return commit, err 286 | } 287 | commit.Author = author 288 | commit.AuthorDate = date 289 | case committerKey: 290 | committerline := string(bytes.Join(parts[1:], []byte(" "))) 291 | committer, date, err := parseCommitterString(committerline) 292 | if err != nil { 293 | return commit, err 294 | } 295 | commit.Committer = committer 296 | commit.CommitterDate = date 297 | default: 298 | err := fmt.Errorf("encountered unknown field in commit: %s", key) 299 | return commit, err 300 | } 301 | } 302 | commit.Name = name 303 | commit.Message = bytes.Join(commitMessageLines, []byte("\n")) 304 | return commit, nil 305 | } 306 | 307 | func parseTree(r io.Reader, resultSize string, basedir os.File) (Tree, error) { 308 | var tree = Tree{_type: "tree", size: resultSize} 309 | 310 | scanner := bufio.NewScanner(r) 311 | scanner.Split(ScanNullLines) 312 | 313 | var tmp objectMeta 314 | 315 | var resultObjs []objectMeta 316 | 317 | for count := 0; ; count++ { 318 | done := !scanner.Scan() 319 | if done { 320 | break 321 | } 322 | 323 | txt := scanner.Text() 324 | 325 | if count == 0 { 326 | // the first time through, scanner.Text() will be 327 | // 328 | // separated by a space 329 | fields := strings.Fields(txt) 330 | tmp.Perms = normalizePerms(fields[0]) 331 | tmp.filename = fields[1] 332 | continue 333 | } 334 | 335 | // after the first time through, scanner.Text() will be 336 | // 337 | // where perms2 and file2 refer to the permissions and filename (respectively) 338 | // of the NEXT object, and is the first 20 bytes exactly. 339 | // If there is no next object (this is the last object) 340 | // then scanner.Text() will yield exactly 20 bytes. 341 | 342 | // decode the next 20 bytes to get the SHA 343 | tmp.Hash = SHA(hex.EncodeToString([]byte(txt[:20]))) 344 | resultObjs = append(resultObjs, tmp) 345 | if len(txt) <= 20 { 346 | // We've read the last line 347 | break 348 | } 349 | 350 | // Now, tmp points to the next object in the tree listing 351 | tmp = objectMeta{} 352 | remainder := txt[20:] 353 | fields := strings.Fields(remainder) 354 | tmp.Perms = normalizePerms(fields[0]) 355 | tmp.filename = fields[1] 356 | } 357 | 358 | if err := scanner.Err(); err != nil && err != io.EOF { 359 | return tree, err 360 | } 361 | 362 | for _, part := range resultObjs { 363 | obj, err := NewObject(part.Hash, basedir) 364 | if err != nil { 365 | return tree, err 366 | } 367 | 368 | if o, ok := obj.(*packObject); ok { 369 | obj, err = o.normalize(basedir) 370 | if err != nil { 371 | return tree, err 372 | } 373 | } 374 | 375 | switch obj.Type() { 376 | case "tree": 377 | tree.Trees = append(tree.Trees, part) 378 | case "blob": 379 | tree.Blobs = append(tree.Blobs, part) 380 | default: 381 | return tree, fmt.Errorf("Unknown type found: %s", obj.Type()) 382 | } 383 | } 384 | return tree, nil 385 | } 386 | 387 | func parseBlob(r io.Reader, resultSize string) (Blob, error) { 388 | var blob = Blob{_type: "blob", size: resultSize} 389 | bts, err := ioutil.ReadAll(r) 390 | blob.Contents = bts 391 | return blob, err 392 | } 393 | 394 | func findUniquePrefix(prefix SHA, files []os.FileInfo) (os.FileInfo, error) { 395 | var result os.FileInfo 396 | for _, file := range files { 397 | if file.IsDir() { 398 | continue 399 | } 400 | if strings.HasPrefix(file.Name(), string(prefix)) { 401 | if result != nil { 402 | return nil, fmt.Errorf("prefix is not unique: %s", prefix) 403 | } 404 | result = file 405 | } 406 | } 407 | if result == nil { 408 | return nil, os.ErrNotExist 409 | } 410 | return result, nil 411 | } 412 | 413 | // The ommitter string is in the same format as 414 | // the author string, and oftentimes shares 415 | // the same value as the author string. 416 | 417 | func parseCommitterString(str string) (committer string, date time.Time, err error) { 418 | return parseAuthorString(str) 419 | } 420 | 421 | // parseAuthorString parses the author string. 422 | func parseAuthorString(str string) (author string, date time.Time, err error) { 423 | const layout = "Mon Jan _2 15:04:05 2006 -0700" 424 | const layout2 = "Mon Jan _2 15:04:05 2006" 425 | var authorW bytes.Buffer 426 | var dateW bytes.Buffer 427 | 428 | s := bufio.NewScanner(strings.NewReader(str)) 429 | s.Split(bufio.ScanBytes) 430 | 431 | // git will ignore '<' if it appears in an author's name 432 | // so we can safely use it as a delimiter 433 | for s.Scan() { 434 | authorW.Write(s.Bytes()) 435 | if s.Text() == ">" { 436 | break 437 | } 438 | } 439 | for s.Scan() { 440 | dateW.Write(s.Bytes()) 441 | } 442 | if s.Err() != nil { 443 | err = s.Err() 444 | return 445 | } 446 | 447 | timestamp, err := strconv.Atoi(strings.Fields(dateW.String())[0]) 448 | if err != nil { 449 | return 450 | } 451 | 452 | timezone := strings.Fields(dateW.String())[1] 453 | 454 | hours, err := strconv.Atoi(timezone) 455 | if err != nil { 456 | return 457 | } 458 | t := time.Unix(int64(timestamp), 0).In(time.FixedZone("", hours*60*60/100)) 459 | date, err = time.Parse(layout, fmt.Sprintf("%s %s", t.Format(layout2), timezone)) 460 | 461 | return strings.TrimSpace(authorW.String()), date, err 462 | } 463 | -------------------------------------------------------------------------------- /test_data/LICENSE: -------------------------------------------------------------------------------- 1 | This license applies to the files `test-delta.c`, `test-delta-new.c, `test-delta-new.delta`, `zlib.c`, `zlib-changed.c`, and `zlib-delta`. 2 | 3 | 4 | ---------------------------------------- 5 | 6 | GNU GENERAL PUBLIC LICENSE 7 | Version 2, June 1991 8 | 9 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 10 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 11 | Everyone is permitted to copy and distribute verbatim copies 12 | of this license document, but changing it is not allowed. 13 | 14 | Preamble 15 | 16 | The licenses for most software are designed to take away your 17 | freedom to share and change it. By contrast, the GNU General Public 18 | License is intended to guarantee your freedom to share and change free 19 | software--to make sure the software is free for all its users. This 20 | General Public License applies to most of the Free Software 21 | Foundation's software and to any other program whose authors commit to 22 | using it. (Some other Free Software Foundation software is covered by 23 | the GNU Lesser General Public License instead.) You can apply it to 24 | your programs, too. 25 | 26 | When we speak of free software, we are referring to freedom, not 27 | price. Our General Public Licenses are designed to make sure that you 28 | have the freedom to distribute copies of free software (and charge for 29 | this service if you wish), that you receive source code or can get it 30 | if you want it, that you can change the software or use pieces of it 31 | in new free programs; and that you know you can do these things. 32 | 33 | To protect your rights, we need to make restrictions that forbid 34 | anyone to deny you these rights or to ask you to surrender the rights. 35 | These restrictions translate to certain responsibilities for you if you 36 | distribute copies of the software, or if you modify it. 37 | 38 | For example, if you distribute copies of such a program, whether 39 | gratis or for a fee, you must give the recipients all the rights that 40 | you have. You must make sure that they, too, receive or can get the 41 | source code. And you must show them these terms so they know their 42 | rights. 43 | 44 | We protect your rights with two steps: (1) copyright the software, and 45 | (2) offer you this license which gives you legal permission to copy, 46 | distribute and/or modify the software. 47 | 48 | Also, for each author's protection and ours, we want to make certain 49 | that everyone understands that there is no warranty for this free 50 | software. If the software is modified by someone else and passed on, we 51 | want its recipients to know that what they have is not the original, so 52 | that any problems introduced by others will not reflect on the original 53 | authors' reputations. 54 | 55 | Finally, any free program is threatened constantly by software 56 | patents. We wish to avoid the danger that redistributors of a free 57 | program will individually obtain patent licenses, in effect making the 58 | program proprietary. To prevent this, we have made it clear that any 59 | patent must be licensed for everyone's free use or not licensed at all. 60 | 61 | The precise terms and conditions for copying, distribution and 62 | modification follow. 63 | 64 | GNU GENERAL PUBLIC LICENSE 65 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 66 | 67 | 0. This License applies to any program or other work which contains 68 | a notice placed by the copyright holder saying it may be distributed 69 | under the terms of this General Public License. The "Program", below, 70 | refers to any such program or work, and a "work based on the Program" 71 | means either the Program or any derivative work under copyright law: 72 | that is to say, a work containing the Program or a portion of it, 73 | either verbatim or with modifications and/or translated into another 74 | language. (Hereinafter, translation is included without limitation in 75 | the term "modification".) Each licensee is addressed as "you". 76 | 77 | Activities other than copying, distribution and modification are not 78 | covered by this License; they are outside its scope. The act of 79 | running the Program is not restricted, and the output from the Program 80 | is covered only if its contents constitute a work based on the 81 | Program (independent of having been made by running the Program). 82 | Whether that is true depends on what the Program does. 83 | 84 | 1. You may copy and distribute verbatim copies of the Program's 85 | source code as you receive it, in any medium, provided that you 86 | conspicuously and appropriately publish on each copy an appropriate 87 | copyright notice and disclaimer of warranty; keep intact all the 88 | notices that refer to this License and to the absence of any warranty; 89 | and give any other recipients of the Program a copy of this License 90 | along with the Program. 91 | 92 | You may charge a fee for the physical act of transferring a copy, and 93 | you may at your option offer warranty protection in exchange for a fee. 94 | 95 | 2. You may modify your copy or copies of the Program or any portion 96 | of it, thus forming a work based on the Program, and copy and 97 | distribute such modifications or work under the terms of Section 1 98 | above, provided that you also meet all of these conditions: 99 | 100 | a) You must cause the modified files to carry prominent notices 101 | stating that you changed the files and the date of any change. 102 | 103 | b) You must cause any work that you distribute or publish, that in 104 | whole or in part contains or is derived from the Program or any 105 | part thereof, to be licensed as a whole at no charge to all third 106 | parties under the terms of this License. 107 | 108 | c) If the modified program normally reads commands interactively 109 | when run, you must cause it, when started running for such 110 | interactive use in the most ordinary way, to print or display an 111 | announcement including an appropriate copyright notice and a 112 | notice that there is no warranty (or else, saying that you provide 113 | a warranty) and that users may redistribute the program under 114 | these conditions, and telling the user how to view a copy of this 115 | License. (Exception: if the Program itself is interactive but 116 | does not normally print such an announcement, your work based on 117 | the Program is not required to print an announcement.) 118 | 119 | These requirements apply to the modified work as a whole. If 120 | identifiable sections of that work are not derived from the Program, 121 | and can be reasonably considered independent and separate works in 122 | themselves, then this License, and its terms, do not apply to those 123 | sections when you distribute them as separate works. But when you 124 | distribute the same sections as part of a whole which is a work based 125 | on the Program, the distribution of the whole must be on the terms of 126 | this License, whose permissions for other licensees extend to the 127 | entire whole, and thus to each and every part regardless of who wrote it. 128 | 129 | Thus, it is not the intent of this section to claim rights or contest 130 | your rights to work written entirely by you; rather, the intent is to 131 | exercise the right to control the distribution of derivative or 132 | collective works based on the Program. 133 | 134 | In addition, mere aggregation of another work not based on the Program 135 | with the Program (or with a work based on the Program) on a volume of 136 | a storage or distribution medium does not bring the other work under 137 | the scope of this License. 138 | 139 | 3. You may copy and distribute the Program (or a work based on it, 140 | under Section 2) in object code or executable form under the terms of 141 | Sections 1 and 2 above provided that you also do one of the following: 142 | 143 | a) Accompany it with the complete corresponding machine-readable 144 | source code, which must be distributed under the terms of Sections 145 | 1 and 2 above on a medium customarily used for software interchange; or, 146 | 147 | b) Accompany it with a written offer, valid for at least three 148 | years, to give any third party, for a charge no more than your 149 | cost of physically performing source distribution, a complete 150 | machine-readable copy of the corresponding source code, to be 151 | distributed under the terms of Sections 1 and 2 above on a medium 152 | customarily used for software interchange; or, 153 | 154 | c) Accompany it with the information you received as to the offer 155 | to distribute corresponding source code. (This alternative is 156 | allowed only for noncommercial distribution and only if you 157 | received the program in object code or executable form with such 158 | an offer, in accord with Subsection b above.) 159 | 160 | The source code for a work means the preferred form of the work for 161 | making modifications to it. For an executable work, complete source 162 | code means all the source code for all modules it contains, plus any 163 | associated interface definition files, plus the scripts used to 164 | control compilation and installation of the executable. However, as a 165 | special exception, the source code distributed need not include 166 | anything that is normally distributed (in either source or binary 167 | form) with the major components (compiler, kernel, and so on) of the 168 | operating system on which the executable runs, unless that component 169 | itself accompanies the executable. 170 | 171 | If distribution of executable or object code is made by offering 172 | access to copy from a designated place, then offering equivalent 173 | access to copy the source code from the same place counts as 174 | distribution of the source code, even though third parties are not 175 | compelled to copy the source along with the object code. 176 | 177 | 4. You may not copy, modify, sublicense, or distribute the Program 178 | except as expressly provided under this License. Any attempt 179 | otherwise to copy, modify, sublicense or distribute the Program is 180 | void, and will automatically terminate your rights under this License. 181 | However, parties who have received copies, or rights, from you under 182 | this License will not have their licenses terminated so long as such 183 | parties remain in full compliance. 184 | 185 | 5. You are not required to accept this License, since you have not 186 | signed it. However, nothing else grants you permission to modify or 187 | distribute the Program or its derivative works. These actions are 188 | prohibited by law if you do not accept this License. Therefore, by 189 | modifying or distributing the Program (or any work based on the 190 | Program), you indicate your acceptance of this License to do so, and 191 | all its terms and conditions for copying, distributing or modifying 192 | the Program or works based on it. 193 | 194 | 6. Each time you redistribute the Program (or any work based on the 195 | Program), the recipient automatically receives a license from the 196 | original licensor to copy, distribute or modify the Program subject to 197 | these terms and conditions. You may not impose any further 198 | restrictions on the recipients' exercise of the rights granted herein. 199 | You are not responsible for enforcing compliance by third parties to 200 | this License. 201 | 202 | 7. If, as a consequence of a court judgment or allegation of patent 203 | infringement or for any other reason (not limited to patent issues), 204 | conditions are imposed on you (whether by court order, agreement or 205 | otherwise) that contradict the conditions of this License, they do not 206 | excuse you from the conditions of this License. If you cannot 207 | distribute so as to satisfy simultaneously your obligations under this 208 | License and any other pertinent obligations, then as a consequence you 209 | may not distribute the Program at all. For example, if a patent 210 | license would not permit royalty-free redistribution of the Program by 211 | all those who receive copies directly or indirectly through you, then 212 | the only way you could satisfy both it and this License would be to 213 | refrain entirely from distribution of the Program. 214 | 215 | If any portion of this section is held invalid or unenforceable under 216 | any particular circumstance, the balance of the section is intended to 217 | apply and the section as a whole is intended to apply in other 218 | circumstances. 219 | 220 | It is not the purpose of this section to induce you to infringe any 221 | patents or other property right claims or to contest validity of any 222 | such claims; this section has the sole purpose of protecting the 223 | integrity of the free software distribution system, which is 224 | implemented by public license practices. Many people have made 225 | generous contributions to the wide range of software distributed 226 | through that system in reliance on consistent application of that 227 | system; it is up to the author/donor to decide if he or she is willing 228 | to distribute software through any other system and a licensee cannot 229 | impose that choice. 230 | 231 | This section is intended to make thoroughly clear what is believed to 232 | be a consequence of the rest of this License. 233 | 234 | 8. If the distribution and/or use of the Program is restricted in 235 | certain countries either by patents or by copyrighted interfaces, the 236 | original copyright holder who places the Program under this License 237 | may add an explicit geographical distribution limitation excluding 238 | those countries, so that distribution is permitted only in or among 239 | countries not thus excluded. In such case, this License incorporates 240 | the limitation as if written in the body of this License. 241 | 242 | 9. The Free Software Foundation may publish revised and/or new versions 243 | of the General Public License from time to time. Such new versions will 244 | be similar in spirit to the present version, but may differ in detail to 245 | address new problems or concerns. 246 | 247 | Each version is given a distinguishing version number. If the Program 248 | specifies a version number of this License which applies to it and "any 249 | later version", you have the option of following the terms and conditions 250 | either of that version or of any later version published by the Free 251 | Software Foundation. If the Program does not specify a version number of 252 | this License, you may choose any version ever published by the Free Software 253 | Foundation. 254 | 255 | 10. If you wish to incorporate parts of the Program into other free 256 | programs whose distribution conditions are different, write to the author 257 | to ask for permission. For software which is copyrighted by the Free 258 | Software Foundation, write to the Free Software Foundation; we sometimes 259 | make exceptions for this. Our decision will be guided by the two goals 260 | of preserving the free status of all derivatives of our free software and 261 | of promoting the sharing and reuse of software generally. 262 | 263 | NO WARRANTY 264 | 265 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 266 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 267 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 268 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 269 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 270 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 271 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 272 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 273 | REPAIR OR CORRECTION. 274 | 275 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 276 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 277 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 278 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 279 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 280 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 281 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 282 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 283 | POSSIBILITY OF SUCH DAMAGES. 284 | 285 | END OF TERMS AND CONDITIONS 286 | 287 | How to Apply These Terms to Your New Programs 288 | 289 | If you develop a new program, and you want it to be of the greatest 290 | possible use to the public, the best way to achieve this is to make it 291 | free software which everyone can redistribute and change under these terms. 292 | 293 | To do so, attach the following notices to the program. It is safest 294 | to attach them to the start of each source file to most effectively 295 | convey the exclusion of warranty; and each file should have at least 296 | the "copyright" line and a pointer to where the full notice is found. 297 | 298 | 299 | Copyright (C) 300 | 301 | This program is free software; you can redistribute it and/or modify 302 | it under the terms of the GNU General Public License as published by 303 | the Free Software Foundation; either version 2 of the License, or 304 | (at your option) any later version. 305 | 306 | This program is distributed in the hope that it will be useful, 307 | but WITHOUT ANY WARRANTY; without even the implied warranty of 308 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 309 | GNU General Public License for more details. 310 | 311 | You should have received a copy of the GNU General Public License along 312 | with this program; if not, write to the Free Software Foundation, Inc., 313 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 314 | 315 | Also add information on how to contact you by electronic and paper mail. 316 | 317 | If the program is interactive, make it output a short notice like this 318 | when it starts in an interactive mode: 319 | 320 | Gnomovision version 69, Copyright (C) year name of author 321 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 322 | This is free software, and you are welcome to redistribute it 323 | under certain conditions; type `show c' for details. 324 | 325 | The hypothetical commands `show w' and `show c' should show the appropriate 326 | parts of the General Public License. Of course, the commands you use may 327 | be called something other than `show w' and `show c'; they could even be 328 | mouse-clicks or menu items--whatever suits your program. 329 | 330 | You should also get your employer (if you work as a programmer) or your 331 | school, if any, to sign a "copyright disclaimer" for the program, if 332 | necessary. Here is a sample; alter the names: 333 | 334 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 335 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 336 | 337 | , 1 April 1989 338 | Ty Coon, President of Vice 339 | 340 | This General Public License does not permit incorporating your program into 341 | proprietary programs. If your program is a subroutine library, you may 342 | consider it more useful to permit linking proprietary applications with the 343 | library. If this is what you want to do, use the GNU Lesser General 344 | Public License instead of this License. 345 | --------------------------------------------------------------------------------