Skip to content

Commit

Permalink
Merge pull request #78 from go-kivik/copyRev
Browse files Browse the repository at this point in the history
Support target rev for copy command
  • Loading branch information
flimzy committed Apr 25, 2021
2 parents f3dcc42 + 49cf632 commit 6a3eac0
Show file tree
Hide file tree
Showing 14 changed files with 159 additions and 9 deletions.
23 changes: 20 additions & 3 deletions cmd/kouchctl/cmd/copy.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (

type copy struct {
*root
targetRev string
}

func copyCmd(r *root) *cobra.Command {
Expand All @@ -36,6 +37,9 @@ func copyCmd(r *root) *cobra.Command {
RunE: c.RunE,
}

pf := cmd.PersistentFlags()
pf.StringVarP(&c.targetRev, "target-rev", "t", "", "The current revision of the target document. May also be provided by appending ?rev=<rev> to the target doc ID")

return cmd
}

Expand All @@ -51,17 +55,25 @@ func (c *copy) RunE(cmd *cobra.Command, args []string) error {
if len(args) < 2 { // nolint:gomnd
return errors.Code(errors.ErrUsage, "missing target")
}
target, _, err := config.ContextFromDSN(args[1])
target, targetOpts, err := config.ContextFromDSN(args[1])
if err != nil {
return fmt.Errorf("invalid target: %w", err)
}
if c.targetRev == "" {
c.targetRev = targetOpts["rev"]
}

c.log.Debugf("[copy] Will copy: %s/%s/%s to %s", client.DSN(), sourceDB, sourceDoc, target.DSN())

source, _ := c.conf.CurrentCx()
if !shouldEmulateCopy(source, target) {
targetDocID := target.DocID
if c.targetRev != "" {
targetDocID += "?rev=" + c.targetRev
}

return c.retry(func() error {
rev, err := client.DB(sourceDB).Copy(cmd.Context(), target.DocID, sourceDoc)
rev, err := client.DB(sourceDB).Copy(cmd.Context(), targetDocID, sourceDoc)
if err != nil {
return err
}
Expand All @@ -75,6 +87,11 @@ func (c *copy) RunE(cmd *cobra.Command, args []string) error {
}

var doc map[string]interface{}
opts := map[string]interface{}{}
if c.targetRev != "" {
opts["rev"] = c.targetRev
}

return c.retry(func() error {
if doc == nil {
row := client.DB(sourceDB).Get(cmd.Context(), sourceDoc, c.opts())
Expand All @@ -85,7 +102,7 @@ func (c *copy) RunE(cmd *cobra.Command, args []string) error {
return err
}
}
rev, err := tClient.DB(target.Database).Put(cmd.Context(), target.DocID, doc)
rev, err := tClient.DB(target.Database).Put(cmd.Context(), target.DocID, doc, opts)
if err != nil {
return err
}
Expand Down
88 changes: 85 additions & 3 deletions cmd/kouchctl/cmd/copy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import (
"github.com/go-kivik/xkivik/v4/cmd/kouchctl/errors"
)

const methodCopy = "COPY"

func Test_copy_RunE(t *testing.T) {
tests := testy.NewTable()

Expand All @@ -45,10 +47,10 @@ func Test_copy_RunE(t *testing.T) {
},
Body: ioutil.NopCloser(strings.NewReader(`{"id": "target","ok": true,"rev": "2-62e778c9ec09214dd685a981dcc24074"}`)),
}, func(t *testing.T, req *http.Request) {
if req.Method != "COPY" {
if req.Method != methodCopy {
t.Errorf("Unexpected method: %v", req.Method)
}
if req.URL.Path != "/jkl/src" {
if req.URL.Path != "/jjj/src" {
t.Errorf("Unexpected path: %s", req.URL.Path)
}
if d := testy.DiffHTTPRequest(testy.Snapshot(t), req, standardReplacements...); d != nil {
Expand All @@ -57,7 +59,7 @@ func Test_copy_RunE(t *testing.T) {
})

return cmdTest{
args: []string{"--debug", "copy", s.URL + "/jkl/src", "target"},
args: []string{"--debug", "copy", s.URL + "/jjj/src", "target"},
}
})
tests.Add("emulated COPY", func(t *testing.T) interface{} {
Expand Down Expand Up @@ -96,6 +98,86 @@ func Test_copy_RunE(t *testing.T) {
args: []string{"--debug", "copy", ss.URL + "/asdf/src", ts.URL + "/qwerty/target"},
}
})
tests.Add("remote COPY with rev", func(t *testing.T) interface{} {
s := testy.ServeResponseValidator(t, &http.Response{
Header: http.Header{
"ETag": {`"2-62e778c9ec09214dd685a981dcc24074"`},
},
Body: ioutil.NopCloser(strings.NewReader(`{"id": "target","ok": true,"rev": "2-62e778c9ec09214dd685a981dcc24074"}`)),
}, func(t *testing.T, req *http.Request) {
if req.Method != methodCopy {
t.Errorf("Unexpected method: %v", req.Method)
}
if req.URL.Path != "/jkl/src" {
t.Errorf("Unexpected path: %s", req.URL.Path)
}
if d := testy.DiffHTTPRequest(testy.Snapshot(t), req, standardReplacements...); d != nil {
t.Error(d)
}
})

return cmdTest{
args: []string{"--debug", "copy", s.URL + "/jkl/src", "target?rev=3-xxx"},
}
})
tests.Add("remote COPY with --target-rev", func(t *testing.T) interface{} {
s := testy.ServeResponseValidator(t, &http.Response{
Header: http.Header{
"ETag": {`"2-62e778c9ec09214dd685a981dcc24074"`},
},
Body: ioutil.NopCloser(strings.NewReader(`{"id": "target","ok": true,"rev": "2-62e778c9ec09214dd685a981dcc24074"}`)),
}, func(t *testing.T, req *http.Request) {
if req.Method != methodCopy {
t.Errorf("Unexpected method: %v", req.Method)
}
if req.URL.Path != "/jkl/src" {
t.Errorf("Unexpected path: %s", req.URL.Path)
}
if d := testy.DiffHTTPRequest(testy.Snapshot(t), req, standardReplacements...); d != nil {
t.Error(d)
}
})

return cmdTest{
args: []string{"--debug", "copy", s.URL + "/jkl/src", "target", "--target-rev", "3-lkjds"},
}
})
tests.Add("emulated COPY with rev", func(t *testing.T) interface{} {
ss := testy.ServeResponseValidator(t, &http.Response{
Header: http.Header{
"Content-Type": {"application/json"},
"ETag": {`"2-62e778c9ec09214dd685a981dcc24074"`},
},
Body: ioutil.NopCloser(strings.NewReader(`{"id": "target","ok": true,"rev": "2-62e778c9ec09214dd685a981dcc24074"}`)),
}, func(t *testing.T, req *http.Request) {
if req.Method != http.MethodGet {
t.Errorf("Unexpected source method: %v", req.Method)
}
if req.URL.Path != "/asdf/src" {
t.Errorf("Unexpected source path: %s", req.URL.Path)
}
})
ts := testy.ServeResponseValidator(t, &http.Response{
Header: http.Header{
"ETag": {`"2-62e778c9ec09214dd685a981dcc24074"`},
},
Body: ioutil.NopCloser(strings.NewReader(`{"id": "target","ok": true,"rev": "2-62e778c9ec09214dd685a981dcc24074"}`)),
}, func(t *testing.T, req *http.Request) {
if req.Method != http.MethodPut {
t.Errorf("Unexpected target method: %v", req.Method)
}
if req.URL.Path != "/qwerty/target" {
t.Errorf("Unexpected target path: %s", req.URL.Path)
}
if d := testy.DiffHTTPRequest(testy.Snapshot(t), req, standardReplacements...); d != nil {
t.Error(d)
}
})

return cmdTest{
args: []string{"--debug", "copy", ss.URL + "/asdf/src", ts.URL + "/qwerty/target?rev=7-qhk"},
}
})

tests.Run(t, func(t *testing.T, tt cmdTest) {
tt.Test(t)
Expand Down
14 changes: 14 additions & 0 deletions cmd/kouchctl/cmd/testdata/Test_copy_RunE_emulated_COPY_with_rev
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
PUT /qwerty/target?rev=7-qhk HTTP/1.1
Host: XXX
Transfer-Encoding: chunked
Accept: application/json
Accept-Encoding: gzip
Content-Length: 69
Content-Type: application/json
User-Agent: Kivik chttp/4.0.0-prerelease (Language=goX.XX.X; Platform=amd64/linux) Kivik/4.0.0-prerelease Kivik CouchDB driver/4.0.0-prerelease

45
{"id":"target","ok":true,"rev":"2-62e778c9ec09214dd685a981dcc24074"}

0

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Debug mode enabled
failed to read kouchconfig: open ~/.kouchctl/config: no such file or directory
[copy] Will copy: http://127.0.0.1:XXX/asdf/src to http://127.0.0.1:XXX/qwerty/target
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
OK: true
ID: target
Rev: 2-62e778c9ec09214dd685a981dcc24074
3 changes: 2 additions & 1 deletion cmd/kouchctl/cmd/testdata/Test_copy_RunE_missing_dsn__stderr
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ Usage:
kouchctl copy [source] [target] [flags]

Flags:
-h, --help help for copy
-h, --help help for copy
-t, --target-rev string The current revision of the target document. May also be provided by appending ?rev=<rev> to the target doc ID

Global Flags:
--connect-timeout string Limits the time spent establishing a TCP connection.
Expand Down
2 changes: 1 addition & 1 deletion cmd/kouchctl/cmd/testdata/Test_copy_RunE_remote_COPY
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
COPY /jkl/src HTTP/1.1
COPY /jjj/src HTTP/1.1
Host: XXX
Accept: application/json
Accept-Encoding: gzip
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
Debug mode enabled
failed to read kouchconfig: open ~/.kouchctl/config: no such file or directory
[copy] Will copy: http://127.0.0.1:XXX/jkl/src to target
[copy] Will copy: http://127.0.0.1:XXX/jjj/src to target
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
COPY /jkl/src HTTP/1.1
Host: XXX
Accept: application/json
Accept-Encoding: gzip
Content-Length: 0
Content-Type: application/json
Destination: target?rev=3-lkjds
User-Agent: Kivik chttp/4.0.0-prerelease (Language=goX.XX.X; Platform=amd64/linux) Kivik/4.0.0-prerelease Kivik CouchDB driver/4.0.0-prerelease

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Debug mode enabled
failed to read kouchconfig: open ~/.kouchctl/config: no such file or directory
[copy] Will copy: http://127.0.0.1:XXX/jkl/src to target
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
OK: true
ID: target
Rev: 2-62e778c9ec09214dd685a981dcc24074
9 changes: 9 additions & 0 deletions cmd/kouchctl/cmd/testdata/Test_copy_RunE_remote_COPY_with_rev
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
COPY /jkl/src HTTP/1.1
Host: XXX
Accept: application/json
Accept-Encoding: gzip
Content-Length: 0
Content-Type: application/json
Destination: target?rev=3-xxx
User-Agent: Kivik chttp/4.0.0-prerelease (Language=goX.XX.X; Platform=amd64/linux) Kivik/4.0.0-prerelease Kivik CouchDB driver/4.0.0-prerelease

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Debug mode enabled
failed to read kouchconfig: open ~/.kouchctl/config: no such file or directory
[copy] Will copy: http://127.0.0.1:XXX/jkl/src to target
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
OK: true
ID: target
Rev: 2-62e778c9ec09214dd685a981dcc24074

0 comments on commit 6a3eac0

Please sign in to comment.