Merge lp:~niemeyer/pyjuju/go-charm-bits into lp:pyjuju/go

Proposed by Gustavo Niemeyer
Status: Merged
Approved by: William Reade
Approved revision: 13
Merged at revision: 12
Proposed branch: lp:~niemeyer/pyjuju/go-charm-bits
Merge into: lp:pyjuju/go
Diff against target: 326 lines (+80/-132)
7 files modified
charm/Makefile (+0/-1)
charm/bundle.go (+9/-0)
charm/dir.go (+49/-49)
charm/dir_test.go (+19/-2)
charm/rel_test.go (+1/-2)
charm/testrepo/dummy/hooks/install (+2/-0)
charm/walk.go (+0/-78)
To merge this branch: bzr merge lp:~niemeyer/pyjuju/go-charm-bits
Reviewer Review Type Date Requested Status
Roger Peppe (community) Approve
William Reade (community) Approve
Review via email: mp+76910@code.launchpad.net

Description of the change

Improvements and fixes in charm directory packing

- Ignore hidden files as the Python version.
- Use the new filepath.Walk interface.
- Bundle dirs as well, so that empty dirs are included.
- Pack unix file mode into the charm bundle.

The last change requires this Go CL to be merged,
so the branch can't be merged before it goes in:

 http://codereview.appspot.com/5124044/

To post a comment you must log in.
lp:~niemeyer/pyjuju/go-charm-bits updated
13. By Gustavo Niemeyer

Bundle directories into the zip as well, so that empty directories are
handled properly.

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

The upstream CL went in already, so one will just need a tip branch to
test this for the moment.

Revision history for this message
William Reade (fwereade) wrote :

[0]

+// join builds a path rooted at the charm's expended directory

s/expended/expanded

Otherwise all looks very clean, +1

review: Approve
Revision history for this message
Roger Peppe (rogpeppe) wrote :

general +1

[1]
charm/bundle.go
 for _, header := range zipr.File {

Seems to me like the body of this loop would be better
in a separate function. Then you could just defer
zf.Close and things would be generally cleaner.

[2]
charm/dir.go
 parts = append([]string{dir.Path}, parts...)

appending to parts directly is bad behaviour because
it could interfere with any callers that call join
with a slice. i know you don't do that, but it sets
a dubious example.

[3]
charm/dir.go
 hidden := len(relpath) > 1 && relpath[0] == '.'

I hope this behaviour is well documented. I've provided
config files as part of a charm before, and it's only
luck that they didn't start with a ".".
If the object is to exclude source-code control files
(e.g. .hg and .bzr) perhaps it would be better to
name the exclusions specifically, rather than excluding
all files starting with ".".

[4]
charm/reltest.go
  got, err := filepath_Rel(test.root, test.path)

i thought Rel had now been accepted.

Revision history for this message
Roger Peppe (rogpeppe) :
review: Needs Fixing
Revision history for this message
Roger Peppe (rogpeppe) :
review: Approve
Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

[1]

That's done. Thanks.

[2]

As we talked online, that's not the case.

[3]

It's not well documented to be honest, but it's easy to go from
more restrictive to more liberal than otherwise. In general depending
on . files at runtime is a bad idea, though, so let's way for someone
to complain so that we can see a real use case.

[4]

It was, but this work was were filepath.Rel was born, so it wasn't
around by the time this branch was pushed. I'll remove the internal
copy now.

Thanks for the review.

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

William, that's done as well, thanks.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'charm/Makefile'
--- charm/Makefile 2011-09-24 15:21:23 +0000
+++ charm/Makefile 2011-09-25 21:15:26 +0000
@@ -10,7 +10,6 @@
10 dir.go\10 dir.go\
11 charm.go\11 charm.go\
12 meta.go\12 meta.go\
13 walk.go\
14 rel.go\13 rel.go\
1514
16GOFMT=gofmt15GOFMT=gofmt
1716
=== modified file 'charm/bundle.go'
--- charm/bundle.go 2011-09-24 15:21:23 +0000
+++ charm/bundle.go 2011-09-25 21:15:26 +0000
@@ -5,6 +5,7 @@
5 "io"5 "io"
6 "os"6 "os"
7 "path/filepath"7 "path/filepath"
8 "strings"
8)9)
910
10// ReadBundle returns a Bundle for the charm in path.11// ReadBundle returns a Bundle for the charm in path.
@@ -129,6 +130,14 @@
129 continue130 continue
130 }131 }
131 path := filepath.Join(dir, filepath.Clean(header.Name))132 path := filepath.Join(dir, filepath.Clean(header.Name))
133 if strings.HasSuffix(header.Name, "/") {
134 err = os.MkdirAll(path, 0755)
135 if err != nil {
136 zf.Close()
137 lasterr = err
138 }
139 continue
140 }
132 base, _ := filepath.Split(path)141 base, _ := filepath.Split(path)
133 err = os.MkdirAll(base, 0755)142 err = os.MkdirAll(base, 0755)
134 if err != nil {143 if err != nil {
135144
=== modified file 'charm/dir.go'
--- charm/dir.go 2011-09-24 15:21:23 +0000
+++ charm/dir.go 2011-09-25 21:15:26 +0000
@@ -43,6 +43,13 @@
43// Trick to ensure *Dir implements the Charm interface.43// Trick to ensure *Dir implements the Charm interface.
44var _ Charm = (*Dir)(nil)44var _ Charm = (*Dir)(nil)
4545
46// join builds a path rooted at the charm's expended directory
47// path and the extra path components provided.
48func (dir *Dir) join(parts ...string) string {
49 parts = append([]string{dir.Path}, parts...)
50 return filepath.Join(parts...)
51}
52
46// Meta returns the Meta representing the metadata.yaml file53// Meta returns the Meta representing the metadata.yaml file
47// for the charm expanded in dir.54// for the charm expanded in dir.
48func (dir *Dir) Meta() *Meta {55func (dir *Dir) Meta() *Meta {
@@ -58,63 +65,56 @@
58// BundleTo creates a charm file from the charm expanded in dir.65// BundleTo creates a charm file from the charm expanded in dir.
59func (dir *Dir) BundleTo(w io.Writer) (err os.Error) {66func (dir *Dir) BundleTo(w io.Writer) (err os.Error) {
60 zipw := zip.NewWriter(w)67 zipw := zip.NewWriter(w)
61 defer func() {68 defer zipw.Close()
62 zipw.Close()69 zp := zipPacker{zipw, dir.Path}
63 handleZipError(&err)70 return filepath.Walk(dir.Path, zp.WalkFunc())
64 }()
65 visitor := zipVisitor{zipw, dir.Path}
66 walk(dir.Path, &visitor)
67 return nil
68}71}
6972
70type zipVisitor struct {73type zipPacker struct {
71 *zip.Writer74 *zip.Writer
72 root string75 root string
73}76}
7477
75func (zipw *zipVisitor) VisitDir(path string, f *os.FileInfo) bool {78func (zp *zipPacker) WalkFunc() filepath.WalkFunc {
76 relpath, err := filepath_Rel(zipw.root, path)79 return func(path string, fi *os.FileInfo, err os.Error) os.Error {
77 zipw.Error(path, err)80 return zp.Visit(path, fi, err)
78 return relpath != "build"81 }
79}82}
8083
81func (zipw *zipVisitor) VisitFile(path string, f *os.FileInfo) {84func (zp *zipPacker) Visit(path string, fi *os.FileInfo, err os.Error) os.Error {
82 relpath, err := filepath_Rel(zipw.root, path)85 if err != nil {
83 zipw.Error(path, err)86 return err
84 w, err := zipw.Create(relpath)87 }
85 zipw.Error(path, err)88 relpath, err := filepath_Rel(zp.root, path)
89 if err != nil {
90 return err
91 }
92 hidden := len(relpath) > 1 && relpath[0] == '.'
93 if fi.IsDirectory() {
94 if relpath == "build" {
95 return filepath.SkipDir
96 }
97 if hidden {
98 return filepath.SkipDir
99 }
100 relpath += "/"
101 }
102 if hidden {
103 return nil
104 }
105 h := &zip.FileHeader{
106 Name: relpath,
107 Method: zip.Deflate,
108 }
109 h.SetMode(fi.Mode)
110 w, err := zp.CreateHeader(h)
111 if err != nil || fi.IsDirectory() {
112 return err
113 }
86 data, err := ioutil.ReadFile(path)114 data, err := ioutil.ReadFile(path)
87 zipw.Error(path, err)115 if err != nil {
116 return err
117 }
88 _, err = w.Write(data)118 _, err = w.Write(data)
89 zipw.Error(path, err)119 return err
90}
91
92type zipError os.Error
93
94func (zipw *zipVisitor) Error(path string, err os.Error) {
95 if err != nil {
96 panic(zipError(err))
97 }
98}
99
100func handleZipError(err *os.Error) {
101 if *err != nil {
102 return // Do not override a previous problem
103 }
104 panicv := recover()
105 if panicv == nil {
106 return
107 }
108 if e, ok := panicv.(zipError); ok {
109 *err = (os.Error)(e)
110 return
111 }
112 panic(panicv) // Something else
113}
114
115// join builds a path rooted at the charm's expended directory
116// path and the extra path components provided.
117func (dir *Dir) join(parts ...string) string {
118 parts = append([]string{dir.Path}, parts...)
119 return filepath.Join(parts...)
120}120}
121121
=== modified file 'charm/dir_test.go'
--- charm/dir_test.go 2011-09-24 15:21:23 +0000
+++ charm/dir_test.go 2011-09-25 21:15:26 +0000
@@ -6,6 +6,7 @@
6 "launchpad.net/juju/go/charm"6 "launchpad.net/juju/go/charm"
7 "os"7 "os"
8 "path/filepath"8 "path/filepath"
9 "syscall"
9)10)
1011
11func repoDir(name string) (path string) {12func repoDir(name string) (path string) {
@@ -34,14 +35,20 @@
34 c.Assert(err, IsNil)35 c.Assert(err, IsNil)
35 defer zipr.Close()36 defer zipr.Close()
3637
37 var metaf *zip.File38 var metaf, instf, emptyf *zip.File
38 for _, f := range zipr.File {39 for _, f := range zipr.File {
39 c.Logf("Bundled file: %s", f.Name)40 c.Logf("Bundled file: %s", f.Name)
40 switch f.Name {41 switch f.Name {
41 case "metadata.yaml":42 case "metadata.yaml":
42 metaf = f43 metaf = f
44 case "hooks/install":
45 instf = f
46 case "empty/":
47 emptyf = f
43 case "build/ignored":48 case "build/ignored":
44 c.Fatal("bundle includes build/*")49 c.Errorf("bundle includes build/*: %s", f.Name)
50 case ".ignored", ".dir/ignored":
51 c.Errorf("bundle includes .* entries: %s", f.Name)
45 }52 }
46 }53 }
4754
@@ -52,4 +59,14 @@
52 meta, err := charm.ReadMeta(reader)59 meta, err := charm.ReadMeta(reader)
53 c.Assert(err, IsNil)60 c.Assert(err, IsNil)
54 c.Assert(meta.Name, Equals, "dummy")61 c.Assert(meta.Name, Equals, "dummy")
62
63 c.Assert(instf, NotNil)
64 mode, err := instf.Mode()
65 c.Assert(err, IsNil)
66 c.Assert(mode & 0777, Equals, uint32(0755))
67
68 c.Assert(emptyf, NotNil)
69 mode, err = emptyf.Mode()
70 c.Assert(err, IsNil)
71 c.Assert(mode & syscall.S_IFMT, Equals, uint32(syscall.S_IFDIR))
55}72}
5673
=== modified file 'charm/rel_test.go'
--- charm/rel_test.go 2011-09-24 15:21:23 +0000
+++ charm/rel_test.go 2011-09-25 21:15:26 +0000
@@ -1,7 +1,6 @@
1package charm1package charm
22
3import (3import (
4 "path/filepath"
5 "runtime"4 "runtime"
6 "testing"5 "testing"
7)6)
@@ -64,7 +63,7 @@
64 tests = append(tests, winreltests...)63 tests = append(tests, winreltests...)
65 }64 }
66 for _, test := range tests {65 for _, test := range tests {
67 got, err := filepath.Rel(test.root, test.path)66 got, err := filepath_Rel(test.root, test.path)
68 if test.want == "err" {67 if test.want == "err" {
69 if err == nil {68 if err == nil {
70 t.Errorf("Rel(%q, %q)=%q, want error", test.root, test.path, got)69 t.Errorf("Rel(%q, %q)=%q, want error", test.root, test.path, got)
7170
=== added directory 'charm/testrepo/dummy/.dir'
=== added file 'charm/testrepo/dummy/.dir/ignored'
=== added directory 'charm/testrepo/dummy/hooks'
=== added file 'charm/testrepo/dummy/hooks/install'
--- charm/testrepo/dummy/hooks/install 1970-01-01 00:00:00 +0000
+++ charm/testrepo/dummy/hooks/install 2011-09-25 21:15:26 +0000
@@ -0,0 +1,2 @@
1#!/bin/bash
2echo "Done!"
03
=== removed file 'charm/walk.go'
--- charm/walk.go 2011-09-24 15:21:23 +0000
+++ charm/walk.go 1970-01-01 00:00:00 +0000
@@ -1,78 +0,0 @@
1// Copyright 2009 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4package charm
5
6import (
7 "os"
8 "path/filepath"
9 "sort"
10)
11
12// Drop in replacement for filepath.Walk while issue 2237 is not solved:
13//
14// http://code.google.com/p/go/issues/detail?id=2237
15
16type walkErrorHandler interface {
17 Error(path string, err os.Error)
18}
19
20// walk walks the file tree rooted at root, calling v.VisitDir or
21// v.VisitFile for each directory or file in the tree, including root.
22// If v.VisitDir returns false, Walk skips the directory's entries;
23// otherwise it invokes itself for each directory entry in sorted order.
24// If the visitor implements the walkErrorHandler interface, any errors
25// found will be dispatched to it.
26func walk(root string, v filepath.Visitor) {
27 f, err := os.Lstat(root)
28 if err != nil {
29 if eh, ok := v.(walkErrorHandler); ok {
30 eh.Error(root, err)
31 }
32 return // can't progress
33 }
34 walk_(root, f, v)
35}
36
37func walk_(path string, f *os.FileInfo, v filepath.Visitor) {
38 if !f.IsDirectory() {
39 v.VisitFile(path, f)
40 return
41 }
42 if !v.VisitDir(path, f) {
43 return // skip directory entries
44 }
45 list, err := readDir(path)
46 if err != nil {
47 if eh, ok := v.(walkErrorHandler); ok {
48 eh.Error(path, err)
49 }
50 }
51 for _, e := range list {
52 walk_(filepath.Join(path, e.Name), e, v)
53 }
54}
55
56func readDir(dirname string) ([]*os.FileInfo, os.Error) {
57 f, err := os.Open(dirname)
58 if err != nil {
59 return nil, err
60 }
61 list, err := f.Readdir(-1)
62 f.Close()
63 if err != nil {
64 return nil, err
65 }
66 fi := make(fileInfoList, len(list))
67 for i := range list {
68 fi[i] = &list[i]
69 }
70 sort.Sort(fi)
71 return fi, nil
72}
73
74type fileInfoList []*os.FileInfo
75
76func (f fileInfoList) Len() int { return len(f) }
77func (f fileInfoList) Less(i, j int) bool { return f[i].Name < f[j].Name }
78func (f fileInfoList) Swap(i, j int) { f[i], f[j] = f[j], f[i] }

Subscribers

People subscribed via source and target branches