{"contents":"package s3\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"io\"\n\t\"time\"\n\n\t\"github.com/go-git/go-git/v5/plumbing\"\n\t\"github.com/go-git/go-git/v5/plumbing/storer\"\n\t\"github.com/go-git/go-git/v5/utils/ioutil\"\n)\n\n// ObjectStorage implements storer.EncodedObjectStorer backed by S3.\n// Each git object is stored as a single S3 object:\n// key: objects/{hash[0:2]}/{hash}\n// value: [type byte][size varint][content bytes]\ntype ObjectStorage struct {\n\tclient *Client\n}\n\nfunc objectKey(h plumbing.Hash) string {\n\ts := h.String()\n\treturn \"objects/\" + s[:2] + \"/\" + s\n}\n\nfunc (o *ObjectStorage) NewEncodedObject() plumbing.EncodedObject {\n\treturn \u0026plumbing.MemoryObject{}\n}\n\nfunc (o *ObjectStorage) SetEncodedObject(obj plumbing.EncodedObject) (plumbing.Hash, error) {\n\th := obj.Hash()\n\tdata, err := encodeObject(obj)\n\tif err != nil {\n\t\treturn plumbing.ZeroHash, err\n\t}\n\t// Objects are immutable and content-addressed; idempotent write.\n\tif err := o.client.Put(context.Background(), objectKey(h), data); err != nil {\n\t\treturn plumbing.ZeroHash, fmt.Errorf(\"s3 put object %s: %w\", h, err)\n\t}\n\treturn h, nil\n}\n\nfunc (o *ObjectStorage) HasEncodedObject(h plumbing.Hash) error {\n\tok, err := o.client.Exists(context.Background(), objectKey(h))\n\tif err != nil {\n\t\treturn err\n\t}\n\tif !ok {\n\t\treturn plumbing.ErrObjectNotFound\n\t}\n\treturn nil\n}\n\nfunc (o *ObjectStorage) EncodedObjectSize(h plumbing.Hash) (int64, error) {\n\tdata, err := o.client.Get(context.Background(), objectKey(h))\n\tif err != nil {\n\t\tif isNotFound(err) {\n\t\t\treturn 0, plumbing.ErrObjectNotFound\n\t\t}\n\t\treturn 0, err\n\t}\n\tobj, err := decodeObject(data)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\treturn obj.Size(), nil\n}\n\nfunc (o *ObjectStorage) EncodedObject(t plumbing.ObjectType, h plumbing.Hash) (plumbing.EncodedObject, error) {\n\tdata, err := o.client.Get(context.Background(), objectKey(h))\n\tif err != nil {\n\t\tif isNotFound(err) {\n\t\t\treturn nil, plumbing.ErrObjectNotFound\n\t\t}\n\t\treturn nil, err\n\t}\n\tobj, err := decodeObject(data)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif t != plumbing.AnyObject \u0026\u0026 obj.Type() != t {\n\t\treturn nil, plumbing.ErrObjectNotFound\n\t}\n\treturn obj, nil\n}\n\nfunc (o *ObjectStorage) IterEncodedObjects(t plumbing.ObjectType) (storer.EncodedObjectIter, error) {\n\tkeys, err := o.client.List(context.Background(), \"objects/\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn \u0026objectIter{\n\t\tclient: o.client,\n\t\tkeys: keys,\n\t\ttyp: t,\n\t}, nil\n}\n\nfunc (o *ObjectStorage) RawObjectWriter(typ plumbing.ObjectType, sz int64) (io.WriteCloser, error) {\n\tobj := o.NewEncodedObject()\n\tobj.SetType(typ)\n\tobj.SetSize(sz)\n\n\tw, err := obj.Writer()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\twc := ioutil.NewWriteCloser(w, \u0026lazyCloser{storage: o, obj: obj, closer: w})\n\treturn wc, nil\n}\n\nfunc (o *ObjectStorage) Begin() storer.Transaction {\n\treturn \u0026txObjectStorage{\n\t\tstorage: o,\n\t\tobjects: make(map[plumbing.Hash]plumbing.EncodedObject),\n\t}\n}\n\nfunc (o *ObjectStorage) ForEachObjectHash(fn func(plumbing.Hash) error) error {\n\tkeys, err := o.client.List(context.Background(), \"objects/\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, k := range keys {\n\t\t// k is \"objects/ab/abcdef...\"\n\t\th := plumbing.NewHash(k[len(k)-40:])\n\t\tif err := fn(h); err != nil {\n\t\t\tif err == storer.ErrStop {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (o *ObjectStorage) ObjectPacks() ([]plumbing.Hash, error) {\n\treturn nil, nil\n}\n\nfunc (o *ObjectStorage) DeleteOldObjectPackAndIndex(plumbing.Hash, time.Time) error {\n\treturn nil\n}\n\nfunc (o *ObjectStorage) LooseObjectTime(plumbing.Hash) (time.Time, error) {\n\treturn time.Time{}, fmt.Errorf(\"not supported\")\n}\n\nfunc (o *ObjectStorage) DeleteLooseObject(plumbing.Hash) error {\n\treturn fmt.Errorf(\"not supported\")\n}\n\nfunc (o *ObjectStorage) AddAlternate(string) error {\n\treturn fmt.Errorf(\"not supported\")\n}\n\n// Encoding: [type:1][size:8 big-endian][content]\n\nfunc encodeObject(obj plumbing.EncodedObject) ([]byte, error) {\n\tr, err := obj.Reader()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer r.Close()\n\n\tcontent, err := io.ReadAll(r)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar buf bytes.Buffer\n\tbuf.WriteByte(byte(obj.Type()))\n\tif err := binary.Write(\u0026buf, binary.BigEndian, obj.Size()); err != nil {\n\t\treturn nil, err\n\t}\n\tbuf.Write(content)\n\treturn buf.Bytes(), nil\n}\n\nfunc decodeObject(data []byte) (plumbing.EncodedObject, error) {\n\tif len(data) \u003c 9 { // 1 byte type + 8 bytes size\n\t\treturn nil, fmt.Errorf(\"s3 object too short: %d bytes\", len(data))\n\t}\n\ttyp := plumbing.ObjectType(data[0])\n\tsize := int64(binary.BigEndian.Uint64(data[1:9]))\n\tcontent := data[9:]\n\n\tobj := \u0026plumbing.MemoryObject{}\n\tobj.SetType(typ)\n\tobj.SetSize(size)\n\tw, err := obj.Writer()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif _, err := w.Write(content); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := w.Close(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn obj, nil\n}\n\n// lazyCloser defers SetEncodedObject until the writer is closed.\ntype lazyCloser struct {\n\tstorage *ObjectStorage\n\tobj plumbing.EncodedObject\n\tcloser io.Closer\n}\n\nfunc (c *lazyCloser) Close() error {\n\tif err := c.closer.Close(); err != nil {\n\t\treturn err\n\t}\n\t_, err := c.storage.SetEncodedObject(c.obj)\n\treturn err\n}\n\n// txObjectStorage buffers writes until Commit.\ntype txObjectStorage struct {\n\tstorage *ObjectStorage\n\tobjects map[plumbing.Hash]plumbing.EncodedObject\n}\n\nfunc (tx *txObjectStorage) SetEncodedObject(obj plumbing.EncodedObject) (plumbing.Hash, error) {\n\th := obj.Hash()\n\ttx.objects[h] = obj\n\treturn h, nil\n}\n\nfunc (tx *txObjectStorage) EncodedObject(t plumbing.ObjectType, h plumbing.Hash) (plumbing.EncodedObject, error) {\n\tobj, ok := tx.objects[h]\n\tif !ok || (t != plumbing.AnyObject \u0026\u0026 obj.Type() != t) {\n\t\treturn nil, plumbing.ErrObjectNotFound\n\t}\n\treturn obj, nil\n}\n\nfunc (tx *txObjectStorage) Commit() error {\n\tfor h, obj := range tx.objects {\n\t\tdelete(tx.objects, h)\n\t\tif _, err := tx.storage.SetEncodedObject(obj); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (tx *txObjectStorage) Rollback() error {\n\ttx.objects = make(map[plumbing.Hash]plumbing.EncodedObject)\n\treturn nil\n}\n\n// objectIter lazily iterates objects from S3.\ntype objectIter struct {\n\tclient *Client\n\tkeys []string\n\tpos int\n\ttyp plumbing.ObjectType\n}\n\nfunc (it *objectIter) Next() (plumbing.EncodedObject, error) {\n\tfor it.pos \u003c len(it.keys) {\n\t\tk := it.keys[it.pos]\n\t\tit.pos++\n\n\t\tdata, err := it.client.Get(context.Background(), k)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tobj, err := decodeObject(data)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif it.typ != plumbing.AnyObject \u0026\u0026 obj.Type() != it.typ {\n\t\t\tcontinue\n\t\t}\n\t\treturn obj, nil\n\t}\n\treturn nil, io.EOF\n}\n\nfunc (it *objectIter) ForEach(fn func(plumbing.EncodedObject) error) error {\n\tfor {\n\t\tobj, err := it.Next()\n\t\tif err == io.EOF {\n\t\t\treturn nil\n\t\t}\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := fn(obj); err != nil {\n\t\t\tif err == storer.ErrStop {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn err\n\t\t}\n\t}\n}\n\nfunc (it *objectIter) Close() {\n\tit.pos = len(it.keys)\n}\n","path":"storage/s3/objects.go","ref":"main"}