Merge lp:~axwalk/juju-core/filestorage-write-tmpdir into lp:~go-bot/juju-core/trunk

Proposed by Andrew Wilkins
Status: Merged
Approved by: Andrew Wilkins
Approved revision: no longer in the source branch.
Merged at revision: 1836
Proposed branch: lp:~axwalk/juju-core/filestorage-write-tmpdir
Merge into: lp:~go-bot/juju-core/trunk
Diff against target: 247 lines (+84/-19)
9 files modified
cmd/juju/synctools.go (+1/-1)
cmd/plugins/juju-metadata/toolsmetadata.go (+1/-1)
environs/filestorage/filestorage.go (+41/-8)
environs/filestorage/filestorage_test.go (+33/-1)
environs/httpstorage/backend_test.go (+1/-1)
environs/testing/storage.go (+1/-1)
environs/tools/simplestreams_test.go (+3/-3)
provider/local/environ.go (+1/-1)
provider/local/storage/worker.go (+2/-2)
To merge this branch: bzr merge lp:~axwalk/juju-core/filestorage-write-tmpdir
Reviewer Review Type Date Requested Status
Tim Penhey (community) Approve
Review via email: mp+185715@code.launchpad.net

Commit message

environs/filestorage: optional atomic Put

As requested in review of manual-bootstrap:
https://code.launchpad.net/~axwalk/juju-core/manual-bootstrap/+merge/184714

A new parameter is added to NewFileStorageWriter
to specify a temporary directory. Put will now
write to a temporary file in this location, and
then move it to the final destination; as long
as the temporary directory exists on the same
filesystem as the storage directory, this should
be atomic.

If the temporary directory is unspecified/blank,
then storagedir+".tmp" will be used to ensure
a "default" of colocation on the storage directory's
filesystem.

While it is not strictly necessary, the temporary
directory location will match that of sshstorage
(merge proposal pending).

https://codereview.appspot.com/13727043/

Description of the change

environs/filestorage: optional atomic Put

As requested in review of manual-bootstrap:
https://code.launchpad.net/~axwalk/juju-core/manual-bootstrap/+merge/184714

A new parameter is added to NewFileStorageWriter
to specify a temporary directory. Put will now
write to a temporary file in this location, and
then move it to the final destination; as long
as the temporary directory exists on the same
filesystem as the storage directory, this should
be atomic.

If the temporary directory is unspecified/blank,
then storagedir+".tmp" will be used to ensure
a "default" of colocation on the storage directory's
filesystem.

While it is not strictly necessary, the temporary
directory location will match that of sshstorage
(merge proposal pending).

https://codereview.appspot.com/13727043/

To post a comment you must log in.
Revision history for this message
Andrew Wilkins (axwalk) wrote :
Download full text (5.8 KiB)

Reviewers: mp+185715_code.launchpad.net,

Message:
Please take a look.

Description:
environs/filestorage: optional atomic Put

As requested in review of manual-bootstrap:
https://code.launchpad.net/~axwalk/juju-core/manual-bootstrap/+merge/184714

A new parameter is added to NewFileStorageWriter
to specify a temporary directory. Put will now
write to a temporary file in this location, and
then move it to the final destination; as long
as the temporary directory exists on the same
filesystem as the storage directory, this should
be atomic.

As with io/ioutil TemporaryFile, if the temporary
directory is blank, os.TempDir will be used to
get the default temporary directory.

https://code.launchpad.net/~axwalk/juju-core/filestorage-write-tmpdir/+merge/185715

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/13727043/
Index: [revision details]
=== added file '[revision details]'
--- [revision details] 2012-01-01 00:00:00 +0000
+++ [revision details] 2012-01-01 00:00:00 +0000
@@ -0,0 +1,2 @@
+Old revision: tarmac-20130916061421-7r11uoyztquho14o
+New revision: <email address hidden>

Index: cmd/juju/synctools.go
=== modified file 'cmd/juju/synctools.go'
--- cmd/juju/synctools.go 2013-09-13 04:16:42 +0000
+++ cmd/juju/synctools.go 2013-09-16 06:41:18 +0000
@@ -83,7 +83,7 @@

   target := environ.Storage()
   if c.destination != "" {
- target, err = filestorage.NewFileStorageWriter(c.destination)
+ target, err = filestorage.NewFileStorageWriter(c.destination, "")
    if err != nil {
     return err
    }

Index: environs/filestorage/filestorage.go
=== modified file 'environs/filestorage/filestorage.go'
--- environs/filestorage/filestorage.go 2013-09-12 05:04:40 +0000
+++ environs/filestorage/filestorage.go 2013-09-16 06:41:18 +0000
@@ -21,7 +21,7 @@
   path string
  }

-// newFileStorageReader returns a new storage reader for
+// NewFileStorageReader returns a new storage reader for
  // a directory inside the local file system.
  func NewFileStorageReader(path string) (environs.StorageReader, error) {
   p := filepath.Clean(path)
@@ -85,14 +85,22 @@

  type fileStorageWriter struct {
   fileStorageReader
+ tmpdir string
  }

-func NewFileStorageWriter(path string) (environs.Storage, error) {
+// NewFileStorageWriter returns a new read/write storag for
+// a directory inside the local file system.
+//
+// A temporary directory may be specified, in which files will be written
+// to before moving to the final destination. If specified, the temporary
+// directory should be on the same filesystem as the storage directory
+// to ensure atomicity. If left unspecified (blank), $TMPDIR will be used.
+func NewFileStorageWriter(path, tmpdir string) (environs.Storage, error) {
   reader, err := NewFileStorageReader(path)
   if err != nil {
    return nil, err
   }
- return &fileStorageWriter{*reader.(*fileStorageReader)}, nil
+ return &fileStorageWriter{*reader.(*fileStorageReader), tmpdir}, nil
  }

  func (f *fileStorageWriter) Put(name string, r io.Reader, length int64)
error {
@@ -101,11 +109,18 @@
   if err := os.MkdirAll(dir, 0755); err != nil && !os.IsExist...

Read more...

Revision history for this message
Andrew Wilkins (axwalk) wrote :
Revision history for this message
Andrew Wilkins (axwalk) wrote :
Revision history for this message
Tim Penhey (thumper) wrote :

https://codereview.appspot.com/13727043/diff/7001/environs/filestorage/filestorage.go
File environs/filestorage/filestorage.go (right):

https://codereview.appspot.com/13727043/diff/7001/environs/filestorage/filestorage.go#newcode107
environs/filestorage/filestorage.go:107: if err := os.MkdirAll(tmpdir,
0755); err != nil && !os.IsExist(err) {
Do you ever remove the tmpdir?

https://codereview.appspot.com/13727043/diff/7001/environs/filestorage/filestorage_test.go
File environs/filestorage/filestorage_test.go (right):

https://codereview.appspot.com/13727043/diff/7001/environs/filestorage/filestorage_test.go#newcode39
environs/filestorage/filestorage_test.go:39: s.writer, err =
filestorage.NewFileStorageWriter(s.dir, "")
how about a constant in filestorage
   const UseDefaultTmpDir = ""

then

s.writer, err = filestorage.NewFileStorageWriter(s.dir,
filestorage.UseDefaultTmpDir)

It means I don't have to go and read NewFileStorageWriter to work out
what "" means.

https://codereview.appspot.com/13727043/

Revision history for this message
Andrew Wilkins (axwalk) wrote :

https://codereview.appspot.com/13727043/diff/7001/environs/filestorage/filestorage.go
File environs/filestorage/filestorage.go (right):

https://codereview.appspot.com/13727043/diff/7001/environs/filestorage/filestorage.go#newcode107
environs/filestorage/filestorage.go:107: if err := os.MkdirAll(tmpdir,
0755); err != nil && !os.IsExist(err) {
On 2013/09/18 04:09:23, thumper wrote:
> Do you ever remove the tmpdir?

No, never. I wasn't sure about this when I implemented it, but thinking
about it some more I think it should create/remove it iff
tmpdir==UseDefaultTmpDir. If a directory is explicitly specified, it
must exist and won't be removed. Then juju sync-tools won't leave tmpdir
turds.

I've updated the implementation, docstring and tests to match this.

https://codereview.appspot.com/13727043/diff/7001/environs/filestorage/filestorage_test.go
File environs/filestorage/filestorage_test.go (right):

https://codereview.appspot.com/13727043/diff/7001/environs/filestorage/filestorage_test.go#newcode39
environs/filestorage/filestorage_test.go:39: s.writer, err =
filestorage.NewFileStorageWriter(s.dir, "")
On 2013/09/18 04:09:23, thumper wrote:
> how about a constant in filestorage
> const UseDefaultTmpDir = ""

> then

> s.writer, err = filestorage.NewFileStorageWriter(s.dir,
> filestorage.UseDefaultTmpDir)

> It means I don't have to go and read NewFileStorageWriter to work out
what ""
> means.

Good idea. Done.

https://codereview.appspot.com/13727043/

Revision history for this message
Andrew Wilkins (axwalk) wrote :
Revision history for this message
Tim Penhey (thumper) wrote :

On 2013/09/18 07:12:02, axw wrote:
> Please take a look.

LGTM

https://codereview.appspot.com/13727043/

Revision history for this message
Tim Penhey (thumper) :
review: Approve
Revision history for this message
Go Bot (go-bot) wrote :

Attempt to merge into lp:juju-core failed due to conflicts:

text conflict in environs/filestorage/filestorage.go
text conflict in environs/filestorage/filestorage_test.go

Revision history for this message
Go Bot (go-bot) wrote :

The attempt to merge lp:~axwalk/juju-core/filestorage-write-tmpdir into lp:juju-core failed. Below is the output from the failed tests.

# launchpad.net/juju-core/provider/local
provider/local/environ.go:169: not enough arguments in call to filestorage.NewFileStorageWriter
# launchpad.net/juju-core/provider/local/storage
provider/local/storage/worker.go:44: not enough arguments in call to filestorage.NewFileStorageWriter
provider/local/storage/worker.go:60: not enough arguments in call to filestorage.NewFileStorageWriter
# launchpad.net/juju-core/environs/testing
environs/testing/storage.go:31: not enough arguments in call to filestorage.NewFileStorageWriter

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'cmd/juju/synctools.go'
--- cmd/juju/synctools.go 2013-09-13 04:16:42 +0000
+++ cmd/juju/synctools.go 2013-09-19 03:28:43 +0000
@@ -83,7 +83,7 @@
8383
84 target := environ.Storage()84 target := environ.Storage()
85 if c.destination != "" {85 if c.destination != "" {
86 target, err = filestorage.NewFileStorageWriter(c.destination)86 target, err = filestorage.NewFileStorageWriter(c.destination, filestorage.UseDefaultTmpDir)
87 if err != nil {87 if err != nil {
88 return err88 return err
89 }89 }
9090
=== modified file 'cmd/plugins/juju-metadata/toolsmetadata.go'
--- cmd/plugins/juju-metadata/toolsmetadata.go 2013-09-16 10:47:28 +0000
+++ cmd/plugins/juju-metadata/toolsmetadata.go 2013-09-19 03:28:43 +0000
@@ -69,7 +69,7 @@
69 return err69 return err
70 }70 }
7171
72 targetStorage, err := filestorage.NewFileStorageWriter(c.metadataDir)72 targetStorage, err := filestorage.NewFileStorageWriter(c.metadataDir, filestorage.UseDefaultTmpDir)
73 if err != nil {73 if err != nil {
74 return err74 return err
75 }75 }
7676
=== modified file 'environs/filestorage/filestorage.go'
--- environs/filestorage/filestorage.go 2013-09-19 00:22:15 +0000
+++ environs/filestorage/filestorage.go 2013-09-19 03:28:43 +0000
@@ -23,7 +23,7 @@
23 path string23 path string
24}24}
2525
26// newFileStorageReader returns a new storage reader for26// NewFileStorageReader returns a new storage reader for
27// a directory inside the local file system.27// a directory inside the local file system.
28func NewFileStorageReader(path string) (storage.StorageReader, error) {28func NewFileStorageReader(path string) (storage.StorageReader, error) {
29 p := filepath.Clean(path)29 p := filepath.Clean(path)
@@ -98,27 +98,60 @@
9898
99type fileStorageWriter struct {99type fileStorageWriter struct {
100 fileStorageReader100 fileStorageReader
101 tmpdir string
101}102}
102103
103func NewFileStorageWriter(path string) (storage.Storage, error) {104// UseDefaultTmpDir may be passed into NewFileStorageWriter
105// for the tmpdir argument, to signify that the default
106// value should be used. See NewFileStorageWriter for more.
107const UseDefaultTmpDir = ""
108
109// NewFileStorageWriter returns a new read/write storag for
110// a directory inside the local file system.
111//
112// A temporary directory may be specified, in which files will be written
113// to before moving to the final destination. If specified, the temporary
114// directory should be on the same filesystem as the storage directory
115// to ensure atomicity. If tmpdir == UseDefaultTmpDir (""), then path+".tmp"
116// will be used.
117//
118// If tmpdir == UseDefaultTmpDir, it will be created when Put is invoked,
119// and will be removed afterwards. If tmpdir != UseDefaultTmpDir, it must
120// already exist, and will never be removed.
121func NewFileStorageWriter(path, tmpdir string) (storage.Storage, error) {
104 reader, err := NewFileStorageReader(path)122 reader, err := NewFileStorageReader(path)
105 if err != nil {123 if err != nil {
106 return nil, err124 return nil, err
107 }125 }
108 return &fileStorageWriter{*reader.(*fileStorageReader)}, nil126 return &fileStorageWriter{*reader.(*fileStorageReader), tmpdir}, nil
109}127}
110128
111func (f *fileStorageWriter) Put(name string, r io.Reader, length int64) error {129func (f *fileStorageWriter) Put(name string, r io.Reader, length int64) error {
112 fullpath := f.fullPath(name)130 fullpath := f.fullPath(name)
113 dir := filepath.Dir(fullpath)131 dir := filepath.Dir(fullpath)
132 tmpdir := f.tmpdir
133 if tmpdir == UseDefaultTmpDir {
134 tmpdir = f.path + ".tmp"
135 if err := os.MkdirAll(tmpdir, 0755); err != nil && !os.IsExist(err) {
136 return err
137 }
138 defer os.Remove(tmpdir)
139 }
114 if err := os.MkdirAll(dir, 0755); err != nil && !os.IsExist(err) {140 if err := os.MkdirAll(dir, 0755); err != nil && !os.IsExist(err) {
115 return err141 return err
116 }142 }
117 data, err := ioutil.ReadAll(r)143 // Write to a temporary file first, and then move (atomically).
118 if err != nil {144 file, err := ioutil.TempFile(tmpdir, "juju-filestorage-")
119 return err145 if err != nil {
120 }146 return err
121 return ioutil.WriteFile(fullpath, data, 0644)147 }
148 _, err = io.CopyN(file, r, length)
149 file.Close()
150 if err != nil {
151 os.Remove(file.Name())
152 return err
153 }
154 return os.Rename(file.Name(), fullpath)
122}155}
123156
124func (f *fileStorageWriter) Remove(name string) error {157func (f *fileStorageWriter) Remove(name string) error {
125158
=== modified file 'environs/filestorage/filestorage_test.go'
--- environs/filestorage/filestorage_test.go 2013-09-19 00:22:15 +0000
+++ environs/filestorage/filestorage_test.go 2013-09-19 03:28:43 +0000
@@ -39,7 +39,7 @@
39 var err error39 var err error
40 s.reader, err = filestorage.NewFileStorageReader(s.dir)40 s.reader, err = filestorage.NewFileStorageReader(s.dir)
41 c.Assert(err, gc.IsNil)41 c.Assert(err, gc.IsNil)
42 s.writer, err = filestorage.NewFileStorageWriter(s.dir)42 s.writer, err = filestorage.NewFileStorageWriter(s.dir, filestorage.UseDefaultTmpDir)
43 c.Assert(err, gc.IsNil)43 c.Assert(err, gc.IsNil)
44}44}
4545
@@ -135,3 +135,35 @@
135 _, err = ioutil.ReadFile(expectedpath)135 _, err = ioutil.ReadFile(expectedpath)
136 c.Assert(err, gc.Not(gc.IsNil))136 c.Assert(err, gc.Not(gc.IsNil))
137}137}
138
139func (s *filestorageSuite) TestPutTmpDir(c *gc.C) {
140 // Put should create and clean up the temporary directory if
141 // tmpdir==UseDefaultTmpDir.
142 err := s.writer.Put("test-write", bytes.NewReader(nil), 0)
143 c.Assert(err, gc.IsNil)
144 _, err = os.Stat(s.dir + ".tmp")
145 c.Assert(err, jc.Satisfies, os.IsNotExist)
146
147 // To deal with recovering from hard failure, UseDefaultTmpDir
148 // doesn't care if the temporary directory already exists. It
149 // still removes it, though.
150 err = os.Mkdir(s.dir+".tmp", 0755)
151 c.Assert(err, gc.IsNil)
152 err = s.writer.Put("test-write", bytes.NewReader(nil), 0)
153 c.Assert(err, gc.IsNil)
154 _, err = os.Stat(s.dir + ".tmp")
155 c.Assert(err, jc.Satisfies, os.IsNotExist)
156
157 // If we explicitly set the temporary directory, it must already exist.
158 s.writer, err = filestorage.NewFileStorageWriter(s.dir, s.dir+".tmp")
159 c.Assert(err, gc.IsNil)
160 err = s.writer.Put("test-write", bytes.NewReader(nil), 0)
161 c.Assert(err, jc.Satisfies, os.IsNotExist)
162 err = os.Mkdir(s.dir+".tmp", 0755)
163 c.Assert(err, gc.IsNil)
164 err = s.writer.Put("test-write", bytes.NewReader(nil), 0)
165 c.Assert(err, gc.IsNil)
166 // Temporary directory should not have been moved.
167 _, err = os.Stat(s.dir + ".tmp")
168 c.Assert(err, gc.IsNil)
169}
138170
=== modified file 'environs/httpstorage/backend_test.go'
--- environs/httpstorage/backend_test.go 2013-09-18 05:32:18 +0000
+++ environs/httpstorage/backend_test.go 2013-09-19 03:28:43 +0000
@@ -36,7 +36,7 @@
36// a base URL for the server and the directory path.36// a base URL for the server and the directory path.
37func startServer(c *gc.C) (listener net.Listener, url, dataDir string) {37func startServer(c *gc.C) (listener net.Listener, url, dataDir string) {
38 dataDir = c.MkDir()38 dataDir = c.MkDir()
39 embedded, err := filestorage.NewFileStorageWriter(dataDir)39 embedded, err := filestorage.NewFileStorageWriter(dataDir, filestorage.UseDefaultTmpDir)
40 c.Assert(err, gc.IsNil)40 c.Assert(err, gc.IsNil)
41 listener, err = httpstorage.Serve("localhost:0", embedded)41 listener, err = httpstorage.Serve("localhost:0", embedded)
42 c.Assert(err, gc.IsNil)42 c.Assert(err, gc.IsNil)
4343
=== modified file 'environs/testing/storage.go'
--- environs/testing/storage.go 2013-09-19 00:22:15 +0000
+++ environs/testing/storage.go 2013-09-19 03:28:43 +0000
@@ -28,7 +28,7 @@
28// directory.28// directory.
29func CreateLocalTestStorage(c *gc.C) (closer io.Closer, stor storage.Storage, dataDir string) {29func CreateLocalTestStorage(c *gc.C) (closer io.Closer, stor storage.Storage, dataDir string) {
30 dataDir = c.MkDir()30 dataDir = c.MkDir()
31 underlying, err := filestorage.NewFileStorageWriter(dataDir)31 underlying, err := filestorage.NewFileStorageWriter(dataDir, filestorage.UseDefaultTmpDir)
32 c.Assert(err, gc.IsNil)32 c.Assert(err, gc.IsNil)
33 listener, err := httpstorage.Serve("localhost:0", underlying)33 listener, err := httpstorage.Serve("localhost:0", underlying)
34 c.Assert(err, gc.IsNil)34 c.Assert(err, gc.IsNil)
3535
=== modified file 'environs/tools/simplestreams_test.go'
--- environs/tools/simplestreams_test.go 2013-09-13 02:02:59 +0000
+++ environs/tools/simplestreams_test.go 2013-09-19 03:28:43 +0000
@@ -284,7 +284,7 @@
284 },284 },
285 }285 }
286 dir := c.MkDir()286 dir := c.MkDir()
287 writer, err := filestorage.NewFileStorageWriter(dir)287 writer, err := filestorage.NewFileStorageWriter(dir, filestorage.UseDefaultTmpDir)
288 c.Assert(err, gc.IsNil)288 c.Assert(err, gc.IsNil)
289 err = tools.WriteMetadata(toolsList, false, writer)289 err = tools.WriteMetadata(toolsList, false, writer)
290 c.Assert(err, gc.IsNil)290 c.Assert(err, gc.IsNil)
@@ -311,7 +311,7 @@
311 URL: "file://" + filepath.Join(dir, "tools/releases/juju-2.0.1-raring-amd64.tgz"),311 URL: "file://" + filepath.Join(dir, "tools/releases/juju-2.0.1-raring-amd64.tgz"),
312 },312 },
313 }313 }
314 writer, err := filestorage.NewFileStorageWriter(dir)314 writer, err := filestorage.NewFileStorageWriter(dir, filestorage.UseDefaultTmpDir)
315 c.Assert(err, gc.IsNil)315 c.Assert(err, gc.IsNil)
316 err = tools.WriteMetadata(toolsList, true, writer)316 err = tools.WriteMetadata(toolsList, true, writer)
317 c.Assert(err, gc.IsNil)317 c.Assert(err, gc.IsNil)
@@ -332,7 +332,7 @@
332 SHA256: "xyz",332 SHA256: "xyz",
333 },333 },
334 }334 }
335 writer, err := filestorage.NewFileStorageWriter(dir)335 writer, err := filestorage.NewFileStorageWriter(dir, filestorage.UseDefaultTmpDir)
336 c.Assert(err, gc.IsNil)336 c.Assert(err, gc.IsNil)
337 err = tools.WriteMetadata(existingToolsList, true, writer)337 err = tools.WriteMetadata(existingToolsList, true, writer)
338 c.Assert(err, gc.IsNil)338 c.Assert(err, gc.IsNil)
339339
=== modified file 'provider/local/environ.go'
--- provider/local/environ.go 2013-09-19 00:22:15 +0000
+++ provider/local/environ.go 2013-09-19 03:28:43 +0000
@@ -166,7 +166,7 @@
166 } else if !info.Mode().IsDir() {166 } else if !info.Mode().IsDir() {
167 return nil, fmt.Errorf("%q exists but is not a directory (and it needs to be)", dir)167 return nil, fmt.Errorf("%q exists but is not a directory (and it needs to be)", dir)
168 }168 }
169 storage, err := filestorage.NewFileStorageWriter(dir)169 storage, err := filestorage.NewFileStorageWriter(dir, filestorage.UseDefaultTmpDir)
170 if err != nil {170 if err != nil {
171 return nil, err171 return nil, err
172 }172 }
173173
=== modified file 'provider/local/storage/worker.go'
--- provider/local/storage/worker.go 2013-09-17 01:33:41 +0000
+++ provider/local/storage/worker.go 2013-09-19 03:28:43 +0000
@@ -41,7 +41,7 @@
41 storageAddr := s.config.Value(agent.StorageAddr)41 storageAddr := s.config.Value(agent.StorageAddr)
42 logger.Infof("serving %s on %s", storageDir, storageAddr)42 logger.Infof("serving %s on %s", storageDir, storageAddr)
4343
44 storage, err := filestorage.NewFileStorageWriter(storageDir)44 storage, err := filestorage.NewFileStorageWriter(storageDir, filestorage.UseDefaultTmpDir)
45 if err != nil {45 if err != nil {
46 logger.Errorf("error with local storage: %v", err)46 logger.Errorf("error with local storage: %v", err)
47 return err47 return err
@@ -57,7 +57,7 @@
57 sharedStorageAddr := s.config.Value(agent.SharedStorageAddr)57 sharedStorageAddr := s.config.Value(agent.SharedStorageAddr)
58 logger.Infof("serving %s on %s", sharedStorageDir, sharedStorageAddr)58 logger.Infof("serving %s on %s", sharedStorageDir, sharedStorageAddr)
5959
60 sharedStorage, err := filestorage.NewFileStorageWriter(sharedStorageDir)60 sharedStorage, err := filestorage.NewFileStorageWriter(sharedStorageDir, filestorage.UseDefaultTmpDir)
61 if err != nil {61 if err != nil {
62 logger.Errorf("error with local storage: %v", err)62 logger.Errorf("error with local storage: %v", err)
63 return err63 return err

Subscribers

People subscribed via source and target branches

to status/vote changes: