mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Move StrSlice to types.
This is a very docker concept that nobody elses need. We only maintain it to keep the API backwards compatible. Signed-off-by: David Calavera <david.calavera@gmail.com>
This commit is contained in:
parent
81c8e4d4c8
commit
f9b857a200
11 changed files with 76 additions and 76 deletions
|
@ -1,4 +1,4 @@
|
|||
package stringutils
|
||||
package strslice
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
@ -65,7 +65,7 @@ func (e *StrSlice) ToString() string {
|
|||
return strings.Join(s, " ")
|
||||
}
|
||||
|
||||
// NewStrSlice creates an StrSlice based on the specified parts (as strings).
|
||||
func NewStrSlice(parts ...string) *StrSlice {
|
||||
// New creates an StrSlice based on the specified parts (as strings).
|
||||
func New(parts ...string) *StrSlice {
|
||||
return &StrSlice{parts}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package stringutils
|
||||
package strslice
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
@ -93,9 +93,9 @@ func TestStrSliceUnmarshalSlice(t *testing.T) {
|
|||
|
||||
func TestStrSliceToString(t *testing.T) {
|
||||
slices := map[*StrSlice]string{
|
||||
NewStrSlice(""): "",
|
||||
NewStrSlice("one"): "one",
|
||||
NewStrSlice("one", "two"): "one two",
|
||||
New(""): "",
|
||||
New("one"): "one",
|
||||
New("one", "two"): "one two",
|
||||
}
|
||||
for s, expected := range slices {
|
||||
toString := s.ToString()
|
||||
|
@ -108,10 +108,10 @@ func TestStrSliceToString(t *testing.T) {
|
|||
func TestStrSliceLen(t *testing.T) {
|
||||
var emptyStrSlice *StrSlice
|
||||
slices := map[*StrSlice]int{
|
||||
NewStrSlice(""): 1,
|
||||
NewStrSlice("one"): 1,
|
||||
NewStrSlice("one", "two"): 2,
|
||||
emptyStrSlice: 0,
|
||||
New(""): 1,
|
||||
New("one"): 1,
|
||||
New("one", "two"): 2,
|
||||
emptyStrSlice: 0,
|
||||
}
|
||||
for s, expected := range slices {
|
||||
if s.Len() != expected {
|
||||
|
@ -123,9 +123,9 @@ func TestStrSliceLen(t *testing.T) {
|
|||
func TestStrSliceSlice(t *testing.T) {
|
||||
var emptyStrSlice *StrSlice
|
||||
slices := map[*StrSlice][]string{
|
||||
NewStrSlice("one"): {"one"},
|
||||
NewStrSlice("one", "two"): {"one", "two"},
|
||||
emptyStrSlice: nil,
|
||||
New("one"): {"one"},
|
||||
New("one", "two"): {"one", "two"},
|
||||
emptyStrSlice: nil,
|
||||
}
|
||||
for s, expected := range slices {
|
||||
if !reflect.DeepEqual(s.Slice(), expected) {
|
|
@ -18,12 +18,12 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/api/types/strslice"
|
||||
"github.com/docker/docker/builder"
|
||||
derr "github.com/docker/docker/errors"
|
||||
flag "github.com/docker/docker/pkg/mflag"
|
||||
"github.com/docker/docker/pkg/nat"
|
||||
"github.com/docker/docker/pkg/signal"
|
||||
"github.com/docker/docker/pkg/stringutils"
|
||||
"github.com/docker/docker/pkg/system"
|
||||
"github.com/docker/docker/runconfig"
|
||||
)
|
||||
|
@ -330,7 +330,7 @@ func run(b *Builder, args []string, attributes map[string]bool, original string)
|
|||
// stash the config environment
|
||||
env := b.runConfig.Env
|
||||
|
||||
defer func(cmd *stringutils.StrSlice) { b.runConfig.Cmd = cmd }(cmd)
|
||||
defer func(cmd *strslice.StrSlice) { b.runConfig.Cmd = cmd }(cmd)
|
||||
defer func(env []string) { b.runConfig.Env = env }(env)
|
||||
|
||||
// derive the net build-time environment for this run. We let config
|
||||
|
@ -373,7 +373,7 @@ func run(b *Builder, args []string, attributes map[string]bool, original string)
|
|||
if len(cmdBuildEnv) > 0 {
|
||||
sort.Strings(cmdBuildEnv)
|
||||
tmpEnv := append([]string{fmt.Sprintf("|%d", len(cmdBuildEnv))}, cmdBuildEnv...)
|
||||
saveCmd = stringutils.NewStrSlice(append(tmpEnv, saveCmd.Slice()...)...)
|
||||
saveCmd = strslice.New(append(tmpEnv, saveCmd.Slice()...)...)
|
||||
}
|
||||
|
||||
b.runConfig.Cmd = saveCmd
|
||||
|
@ -431,7 +431,7 @@ func cmd(b *Builder, args []string, attributes map[string]bool, original string)
|
|||
}
|
||||
}
|
||||
|
||||
b.runConfig.Cmd = stringutils.NewStrSlice(cmdSlice...)
|
||||
b.runConfig.Cmd = strslice.New(cmdSlice...)
|
||||
|
||||
if err := b.commit("", b.runConfig.Cmd, fmt.Sprintf("CMD %q", cmdSlice)); err != nil {
|
||||
return err
|
||||
|
@ -462,16 +462,16 @@ func entrypoint(b *Builder, args []string, attributes map[string]bool, original
|
|||
switch {
|
||||
case attributes["json"]:
|
||||
// ENTRYPOINT ["echo", "hi"]
|
||||
b.runConfig.Entrypoint = stringutils.NewStrSlice(parsed...)
|
||||
b.runConfig.Entrypoint = strslice.New(parsed...)
|
||||
case len(parsed) == 0:
|
||||
// ENTRYPOINT []
|
||||
b.runConfig.Entrypoint = nil
|
||||
default:
|
||||
// ENTRYPOINT echo hi
|
||||
if runtime.GOOS != "windows" {
|
||||
b.runConfig.Entrypoint = stringutils.NewStrSlice("/bin/sh", "-c", parsed[0])
|
||||
b.runConfig.Entrypoint = strslice.New("/bin/sh", "-c", parsed[0])
|
||||
} else {
|
||||
b.runConfig.Entrypoint = stringutils.NewStrSlice("cmd", "/S", "/C", parsed[0])
|
||||
b.runConfig.Entrypoint = strslice.New("cmd", "/S", "/C", parsed[0])
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/api"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/strslice"
|
||||
"github.com/docker/docker/builder"
|
||||
"github.com/docker/docker/builder/dockerfile/parser"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
|
@ -30,14 +31,13 @@ import (
|
|||
"github.com/docker/docker/pkg/progress"
|
||||
"github.com/docker/docker/pkg/streamformatter"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/docker/pkg/stringutils"
|
||||
"github.com/docker/docker/pkg/system"
|
||||
"github.com/docker/docker/pkg/tarsum"
|
||||
"github.com/docker/docker/pkg/urlutil"
|
||||
"github.com/docker/docker/runconfig"
|
||||
)
|
||||
|
||||
func (b *Builder) commit(id string, autoCmd *stringutils.StrSlice, comment string) error {
|
||||
func (b *Builder) commit(id string, autoCmd *strslice.StrSlice, comment string) error {
|
||||
if b.disableCommit {
|
||||
return nil
|
||||
}
|
||||
|
@ -48,11 +48,11 @@ func (b *Builder) commit(id string, autoCmd *stringutils.StrSlice, comment strin
|
|||
if id == "" {
|
||||
cmd := b.runConfig.Cmd
|
||||
if runtime.GOOS != "windows" {
|
||||
b.runConfig.Cmd = stringutils.NewStrSlice("/bin/sh", "-c", "#(nop) "+comment)
|
||||
b.runConfig.Cmd = strslice.New("/bin/sh", "-c", "#(nop) "+comment)
|
||||
} else {
|
||||
b.runConfig.Cmd = stringutils.NewStrSlice("cmd", "/S /C", "REM (nop) "+comment)
|
||||
b.runConfig.Cmd = strslice.New("cmd", "/S /C", "REM (nop) "+comment)
|
||||
}
|
||||
defer func(cmd *stringutils.StrSlice) { b.runConfig.Cmd = cmd }(cmd)
|
||||
defer func(cmd *strslice.StrSlice) { b.runConfig.Cmd = cmd }(cmd)
|
||||
|
||||
hit, err := b.probeCache()
|
||||
if err != nil {
|
||||
|
@ -171,11 +171,11 @@ func (b *Builder) runContextCommand(args []string, allowRemote bool, allowLocalD
|
|||
|
||||
cmd := b.runConfig.Cmd
|
||||
if runtime.GOOS != "windows" {
|
||||
b.runConfig.Cmd = stringutils.NewStrSlice("/bin/sh", "-c", fmt.Sprintf("#(nop) %s %s in %s", cmdName, srcHash, dest))
|
||||
b.runConfig.Cmd = strslice.New("/bin/sh", "-c", fmt.Sprintf("#(nop) %s %s in %s", cmdName, srcHash, dest))
|
||||
} else {
|
||||
b.runConfig.Cmd = stringutils.NewStrSlice("cmd", "/S", "/C", fmt.Sprintf("REM (nop) %s %s in %s", cmdName, srcHash, dest))
|
||||
b.runConfig.Cmd = strslice.New("cmd", "/S", "/C", fmt.Sprintf("REM (nop) %s %s in %s", cmdName, srcHash, dest))
|
||||
}
|
||||
defer func(cmd *stringutils.StrSlice) { b.runConfig.Cmd = cmd }(cmd)
|
||||
defer func(cmd *strslice.StrSlice) { b.runConfig.Cmd = cmd }(cmd)
|
||||
|
||||
if hit, err := b.probeCache(); err != nil {
|
||||
return err
|
||||
|
|
|
@ -23,6 +23,7 @@ import (
|
|||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
registrytypes "github.com/docker/docker/api/types/registry"
|
||||
"github.com/docker/docker/api/types/strslice"
|
||||
"github.com/docker/docker/container"
|
||||
"github.com/docker/docker/daemon/events"
|
||||
"github.com/docker/docker/daemon/exec"
|
||||
|
@ -53,7 +54,6 @@ import (
|
|||
"github.com/docker/docker/pkg/signal"
|
||||
"github.com/docker/docker/pkg/streamformatter"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/docker/pkg/stringutils"
|
||||
"github.com/docker/docker/pkg/sysinfo"
|
||||
"github.com/docker/docker/pkg/system"
|
||||
"github.com/docker/docker/pkg/truncindex"
|
||||
|
@ -479,7 +479,7 @@ func (daemon *Daemon) generateHostname(id string, config *runconfig.Config) {
|
|||
}
|
||||
}
|
||||
|
||||
func (daemon *Daemon) getEntrypointAndArgs(configEntrypoint *stringutils.StrSlice, configCmd *stringutils.StrSlice) (string, []string) {
|
||||
func (daemon *Daemon) getEntrypointAndArgs(configEntrypoint *strslice.StrSlice, configCmd *strslice.StrSlice) (string, []string) {
|
||||
cmdSlice := configCmd.Slice()
|
||||
if configEntrypoint.Len() != 0 {
|
||||
eSlice := configEntrypoint.Slice()
|
||||
|
|
|
@ -6,13 +6,13 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/api/types/strslice"
|
||||
"github.com/docker/docker/container"
|
||||
"github.com/docker/docker/daemon/exec"
|
||||
"github.com/docker/docker/daemon/execdriver"
|
||||
derr "github.com/docker/docker/errors"
|
||||
"github.com/docker/docker/pkg/pools"
|
||||
"github.com/docker/docker/pkg/promise"
|
||||
"github.com/docker/docker/pkg/stringutils"
|
||||
"github.com/docker/docker/runconfig"
|
||||
)
|
||||
|
||||
|
@ -85,8 +85,8 @@ func (d *Daemon) ContainerExecCreate(config *runconfig.ExecConfig) (string, erro
|
|||
return "", err
|
||||
}
|
||||
|
||||
cmd := stringutils.NewStrSlice(config.Cmd...)
|
||||
entrypoint, args := d.getEntrypointAndArgs(stringutils.NewStrSlice(), cmd)
|
||||
cmd := strslice.New(config.Cmd...)
|
||||
entrypoint, args := d.getEntrypointAndArgs(strslice.New(), cmd)
|
||||
|
||||
processConfig := &execdriver.ProcessConfig{
|
||||
CommonProcessConfig: execdriver.CommonProcessConfig{
|
||||
|
|
|
@ -3,8 +3,8 @@ package runconfig
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types/strslice"
|
||||
"github.com/docker/docker/pkg/nat"
|
||||
"github.com/docker/docker/pkg/stringutils"
|
||||
)
|
||||
|
||||
// Just to make life easier
|
||||
|
@ -33,12 +33,12 @@ func TestCompare(t *testing.T) {
|
|||
volumes3["/test3"] = struct{}{}
|
||||
envs1 := []string{"ENV1=value1", "ENV2=value2"}
|
||||
envs2 := []string{"ENV1=value1", "ENV3=value3"}
|
||||
entrypoint1 := stringutils.NewStrSlice("/bin/sh", "-c")
|
||||
entrypoint2 := stringutils.NewStrSlice("/bin/sh", "-d")
|
||||
entrypoint3 := stringutils.NewStrSlice("/bin/sh", "-c", "echo")
|
||||
cmd1 := stringutils.NewStrSlice("/bin/sh", "-c")
|
||||
cmd2 := stringutils.NewStrSlice("/bin/sh", "-d")
|
||||
cmd3 := stringutils.NewStrSlice("/bin/sh", "-c", "echo")
|
||||
entrypoint1 := strslice.New("/bin/sh", "-c")
|
||||
entrypoint2 := strslice.New("/bin/sh", "-d")
|
||||
entrypoint3 := strslice.New("/bin/sh", "-c", "echo")
|
||||
cmd1 := strslice.New("/bin/sh", "-c")
|
||||
cmd2 := strslice.New("/bin/sh", "-d")
|
||||
cmd3 := strslice.New("/bin/sh", "-c", "echo")
|
||||
labels1 := map[string]string{"LABEL1": "value1", "LABEL2": "value2"}
|
||||
labels2 := map[string]string{"LABEL1": "value1", "LABEL2": "value3"}
|
||||
labels3 := map[string]string{"LABEL1": "value1", "LABEL2": "value2", "LABEL3": "value3"}
|
||||
|
|
|
@ -5,8 +5,8 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/docker/docker/api/types/strslice"
|
||||
"github.com/docker/docker/pkg/nat"
|
||||
"github.com/docker/docker/pkg/stringutils"
|
||||
"github.com/docker/docker/volume"
|
||||
)
|
||||
|
||||
|
@ -29,12 +29,12 @@ type Config struct {
|
|||
OpenStdin bool // Open stdin
|
||||
StdinOnce bool // If true, close stdin after the 1 attached client disconnects.
|
||||
Env []string // List of environment variable to set in the container
|
||||
Cmd *stringutils.StrSlice // Command to run when starting the container
|
||||
Cmd *strslice.StrSlice // Command to run when starting the container
|
||||
ArgsEscaped bool `json:",omitempty"` // True if command is already escaped (Windows specific)
|
||||
Image string // Name of the image as it was passed by the operator (eg. could be symbolic)
|
||||
Volumes map[string]struct{} // List of volumes (mounts) used for the container
|
||||
WorkingDir string // Current directory (PWD) in the command will be launched
|
||||
Entrypoint *stringutils.StrSlice // Entrypoint to run when starting the container
|
||||
Entrypoint *strslice.StrSlice // Entrypoint to run when starting the container
|
||||
NetworkDisabled bool `json:",omitempty"` // Is network disabled
|
||||
MacAddress string `json:",omitempty"` // Mac Address of the container
|
||||
OnBuild []string // ONBUILD metadata that were defined on the image Dockerfile
|
||||
|
|
|
@ -9,12 +9,12 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/pkg/stringutils"
|
||||
"github.com/docker/docker/api/types/strslice"
|
||||
)
|
||||
|
||||
type f struct {
|
||||
file string
|
||||
entrypoint *stringutils.StrSlice
|
||||
entrypoint *strslice.StrSlice
|
||||
}
|
||||
|
||||
func TestDecodeContainerConfig(t *testing.T) {
|
||||
|
@ -27,14 +27,14 @@ func TestDecodeContainerConfig(t *testing.T) {
|
|||
if runtime.GOOS != "windows" {
|
||||
image = "ubuntu"
|
||||
fixtures = []f{
|
||||
{"fixtures/unix/container_config_1_14.json", stringutils.NewStrSlice()},
|
||||
{"fixtures/unix/container_config_1_17.json", stringutils.NewStrSlice("bash")},
|
||||
{"fixtures/unix/container_config_1_19.json", stringutils.NewStrSlice("bash")},
|
||||
{"fixtures/unix/container_config_1_14.json", strslice.New()},
|
||||
{"fixtures/unix/container_config_1_17.json", strslice.New("bash")},
|
||||
{"fixtures/unix/container_config_1_19.json", strslice.New("bash")},
|
||||
}
|
||||
} else {
|
||||
image = "windows"
|
||||
fixtures = []f{
|
||||
{"fixtures/windows/container_config_1_19.json", stringutils.NewStrSlice("cmd")},
|
||||
{"fixtures/windows/container_config_1_19.json", strslice.New("cmd")},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,9 +5,9 @@ import (
|
|||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/api/types/strslice"
|
||||
"github.com/docker/docker/pkg/blkiodev"
|
||||
"github.com/docker/docker/pkg/nat"
|
||||
"github.com/docker/docker/pkg/stringutils"
|
||||
"github.com/docker/docker/pkg/ulimit"
|
||||
)
|
||||
|
||||
|
@ -207,24 +207,24 @@ type HostConfig struct {
|
|||
VolumesFrom []string // List of volumes to take from other container
|
||||
|
||||
// Applicable to UNIX platforms
|
||||
CapAdd *stringutils.StrSlice // List of kernel capabilities to add to the container
|
||||
CapDrop *stringutils.StrSlice // List of kernel capabilities to remove from the container
|
||||
DNS []string `json:"Dns"` // List of DNS server to lookup
|
||||
DNSOptions []string `json:"DnsOptions"` // List of DNSOption to look for
|
||||
DNSSearch []string `json:"DnsSearch"` // List of DNSSearch to look for
|
||||
ExtraHosts []string // List of extra hosts
|
||||
GroupAdd []string // List of additional groups that the container process will run as
|
||||
IpcMode IpcMode // IPC namespace to use for the container
|
||||
Links []string // List of links (in the name:alias form)
|
||||
OomScoreAdj int // Container preference for OOM-killing
|
||||
PidMode PidMode // PID namespace to use for the container
|
||||
Privileged bool // Is the container in privileged mode
|
||||
PublishAllPorts bool // Should docker publish all exposed port for the container
|
||||
ReadonlyRootfs bool // Is the container root filesystem in read-only
|
||||
SecurityOpt []string // List of string values to customize labels for MLS systems, such as SELinux.
|
||||
Tmpfs map[string]string `json:",omitempty"` // List of tmpfs (mounts) used for the container
|
||||
UTSMode UTSMode // UTS namespace to use for the container
|
||||
ShmSize *int64 // Total shm memory usage
|
||||
CapAdd *strslice.StrSlice // List of kernel capabilities to add to the container
|
||||
CapDrop *strslice.StrSlice // List of kernel capabilities to remove from the container
|
||||
DNS []string `json:"Dns"` // List of DNS server to lookup
|
||||
DNSOptions []string `json:"DnsOptions"` // List of DNSOption to look for
|
||||
DNSSearch []string `json:"DnsSearch"` // List of DNSSearch to look for
|
||||
ExtraHosts []string // List of extra hosts
|
||||
GroupAdd []string // List of additional groups that the container process will run as
|
||||
IpcMode IpcMode // IPC namespace to use for the container
|
||||
Links []string // List of links (in the name:alias form)
|
||||
OomScoreAdj int // Container preference for OOM-killing
|
||||
PidMode PidMode // PID namespace to use for the container
|
||||
Privileged bool // Is the container in privileged mode
|
||||
PublishAllPorts bool // Should docker publish all exposed port for the container
|
||||
ReadonlyRootfs bool // Is the container root filesystem in read-only
|
||||
SecurityOpt []string // List of string values to customize labels for MLS systems, such as SELinux.
|
||||
Tmpfs map[string]string `json:",omitempty"` // List of tmpfs (mounts) used for the container
|
||||
UTSMode UTSMode // UTS namespace to use for the container
|
||||
ShmSize *int64 // Total shm memory usage
|
||||
|
||||
// Applicable to Windows
|
||||
ConsoleSize [2]int // Initial console size
|
||||
|
|
|
@ -6,13 +6,13 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/api/types/strslice"
|
||||
"github.com/docker/docker/opts"
|
||||
flag "github.com/docker/docker/pkg/mflag"
|
||||
"github.com/docker/docker/pkg/mount"
|
||||
"github.com/docker/docker/pkg/nat"
|
||||
"github.com/docker/docker/pkg/parsers"
|
||||
"github.com/docker/docker/pkg/signal"
|
||||
"github.com/docker/docker/pkg/stringutils"
|
||||
"github.com/docker/docker/volume"
|
||||
"github.com/docker/go-units"
|
||||
)
|
||||
|
@ -249,15 +249,15 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
|
|||
|
||||
var (
|
||||
parsedArgs = cmd.Args()
|
||||
runCmd *stringutils.StrSlice
|
||||
entrypoint *stringutils.StrSlice
|
||||
runCmd *strslice.StrSlice
|
||||
entrypoint *strslice.StrSlice
|
||||
image = cmd.Arg(0)
|
||||
)
|
||||
if len(parsedArgs) > 1 {
|
||||
runCmd = stringutils.NewStrSlice(parsedArgs[1:]...)
|
||||
runCmd = strslice.New(parsedArgs[1:]...)
|
||||
}
|
||||
if *flEntrypoint != "" {
|
||||
entrypoint = stringutils.NewStrSlice(*flEntrypoint)
|
||||
entrypoint = strslice.New(*flEntrypoint)
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -416,8 +416,8 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
|
|||
IpcMode: ipcMode,
|
||||
PidMode: pidMode,
|
||||
UTSMode: utsMode,
|
||||
CapAdd: stringutils.NewStrSlice(flCapAdd.GetAll()...),
|
||||
CapDrop: stringutils.NewStrSlice(flCapDrop.GetAll()...),
|
||||
CapAdd: strslice.New(flCapAdd.GetAll()...),
|
||||
CapDrop: strslice.New(flCapDrop.GetAll()...),
|
||||
GroupAdd: flGroupAdd.GetAll(),
|
||||
RestartPolicy: restartPolicy,
|
||||
SecurityOpt: flSecurityOpt.GetAll(),
|
||||
|
|
Loading…
Reference in a new issue