Merge lp:~rogpeppe/godeps/002-multi-dependency-update into lp:godeps

Proposed by Roger Peppe on 2015-01-27
Status: Needs review
Proposed branch: lp:~rogpeppe/godeps/002-multi-dependency-update
Merge into: lp:godeps
Diff against target: 234 lines (+134/-14)
1 file modified
godeps.go (+134/-14)
To merge this branch: bzr merge lp:~rogpeppe/godeps/002-multi-dependency-update
Reviewer Review Type Date Requested Status
godeps-maintainers 2015-01-27 Pending
Review via email: mp+247684@code.launchpad.net

Description of the Change

allow updating dependencies from multiple files

When there's a conflict, it prints a warning and
tries to choose the dependency from the earliest-specified
file or latest revision depending on command line flags.

Also changed the behaviour of the -n flag
so that it does not overlap so much with -x
and so that we don't see so much noise when experimenting
with godeps -u commands.

https://codereview.appspot.com/197110043/

To post a comment you must log in.
Roger Peppe (rogpeppe) wrote :

Reviewers: mp+247684_code.launchpad.net,

Message:
Please take a look.

Description:
allow updating dependencies from multiple files

When there's a conflict, it prints a warning and
tries to choose the latest dependency.

https://code.launchpad.net/~rogpeppe/godeps/002-multi-dependency-update/+merge/247684

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/197110043/

Affected files (+121, -9 lines):
   A [revision details]
   M godeps.go

Matthew Williams (mattyw) wrote :

Some small comments from me

https://codereview.appspot.com/197110043/diff/1/godeps.go
File godeps.go (right):

https://codereview.appspot.com/197110043/diff/1/godeps.go#newcode43
godeps.go:43: godeps -U [flags] file...
There should be more information about how it works in the usage
message.

e.g.

-U takes multiple dependency files, any common dependcies will be
updated to the latest revision

https://codereview.appspot.com/197110043/diff/1/godeps.go#newcode776
godeps.go:776: fmt.Printf("warning: cannot parse revision number %s:
%v", i0.revno, err0)
So if we can't parse a revision id we always assume that i0 is the
latest. What situations is this likely to fail? I can't help but think
we should return an error at this point. We then have the choice of
stopping the whole thing - or skipping this project and printing a
warning. It feels a bit consistent otherwise.
If we left it like this the usage would be:

-U takes multiple dependency files, any common dependcies will be
updated to the
latest revision. If a particular revid can't be parsed we assume the
first revid we find is the one we want. This means the list of
dependency files can be effectively ordered from most important -> least
important

https://codereview.appspot.com/197110043/

29. By Roger Peppe on 2015-01-27

add latest flag, docs; change -dryrun behaviour slightly

Roger Peppe (rogpeppe) wrote :
Roger Peppe (rogpeppe) wrote :

Please take a look.

https://codereview.appspot.com/197110043/diff/1/godeps.go
File godeps.go (right):

https://codereview.appspot.com/197110043/diff/1/godeps.go#newcode43
godeps.go:43: godeps -U [flags] file...
On 2015/01/27 10:51:26, mattyw wrote:
> There should be more information about how it works in the usage
message.

> e.g.

> -U takes multiple dependency files, any common dependcies will be
updated to the
> latest revision

Done.

https://codereview.appspot.com/197110043/diff/1/godeps.go#newcode776
godeps.go:776: fmt.Printf("warning: cannot parse revision number %s:
%v", i0.revno, err0)
On 2015/01/27 10:51:26, mattyw wrote:
> So if we can't parse a revision id we always assume that i0 is the
latest.

That's not quite right. If we can't parse a revision id, we assume that
the one that parses OK is the latest. If we can't parse either of them,
we always return false.

> What
> situations is this likely to fail? I can't help but think we should
return an
> error at this point. We then have the choice of stopping the whole
thing - or
> skipping this project and printing a warning. It feels a bit
consistent
> otherwise.
> If we left it like this the usage would be:

> -U takes multiple dependency files, any common dependcies will be
updated to the
> latest revision. If a particular revid can't be parsed we assume the
first revid
> we find is the one we want. This means the list of dependency files
can be
> effectively ordered from most important -> least important

I think this is a good idea. I have now changed the behaviour so that by
default
it uses the earliest revision file stated, which makes it easy to update
a bunch
of projects while keeping deps for a particular project intact.

Unfortunately if this is desired (for example we want to give juju-core
deps precedence), there's nothing to stop the highest-precedence project
itself being updated. We have
potential cyclic project dependencies here, and updating dependencies
can
affect the dependency graph itself. Some thought is required.

I've also added a -latest flag to enable the previous behaviour
(updating
to latest dependencies), which will also be useful, I think.

https://codereview.appspot.com/197110043/

Matthew Williams (mattyw) wrote :

two small nitpicks - otherwise LGTM

https://codereview.appspot.com/197110043/diff/40001/godeps.go
File godeps.go (right):

https://codereview.appspot.com/197110043/diff/40001/godeps.go#newcode65
godeps.go:65: a warning will be printed and godeps will choose the
first dependency
two spaces between and godeps

https://codereview.appspot.com/197110043/diff/40001/godeps.go#newcode781
godeps.go:781: func laterIntRevno(i0, i1 VCSInfo) bool {
I'd quite like a TODO here (or near the top) stating the reasons in your
explanation for leaving this as it is. Just as a reminder in case no one
thinks about this issue for 6 months.

https://codereview.appspot.com/197110043/

Unmerged revisions

29. By Roger Peppe on 2015-01-27

add latest flag, docs; change -dryrun behaviour slightly

28. By Roger Peppe on 2015-01-27

godeps: allow multiple dependency files to be updated

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'godeps.go'
2--- godeps.go 2014-12-04 16:32:37 +0000
3+++ godeps.go 2015-01-27 17:42:53 +0000
4@@ -23,9 +23,11 @@
5
6 var (
7 revFile = flag.String("u", "", "update dependencies")
8+ updateMany = flag.Bool("U", false, "update many dependencies")
9 testDeps = flag.Bool("t", false, "include testing dependencies")
10 printCommands = flag.Bool("x", false, "show executed commands")
11- dryRun = flag.Bool("n", false, "print but do not execute update commands")
12+ latest = flag.Bool("latest", false, "use latest dependency version on update conflicts")
13+ dryRun = flag.Bool("n", false, "do not execute update commands but pretend they have run successfully")
14 _ = flag.Bool("f", true, "(deprecated, superceded by -F) when updating, try to fetch deps if the update fails")
15 noFetch = flag.Bool("F", false, "when updating, do not try to fetch deps if the update fails")
16 parallel = flag.Int("P", 1, "max number of concurrent updates")
17@@ -39,6 +41,7 @@
18 Usage:
19 godeps [flags] [pkg ...]
20 godeps -u file [flags]
21+ godeps -U [flags] file...
22
23 In the first form of usage (without the -u flag), godeps prints to
24 standard output a list of all the source dependencies of the named
25@@ -54,6 +57,14 @@
26 than one line for the same package root. If a specified revision is not
27 currently available, godeps will attempt to fetch it, unless the -F flag
28 is provided.
29+
30+In the third form, the sources will be updated to versions specified
31+by any of the given files, which should all hold version information
32+in the same form printed by godeps. If there is a dependency conflict
33+(that is, more than one dependency is found for a given package root),
34+a warning will be printed and godeps will choose the first dependency
35+found. If the -latest flag is specified, it will instead update to
36+the latest revision number or revision date found in the files.
37 `[1:]
38
39 func main() {
40@@ -63,11 +74,20 @@
41 os.Exit(2)
42 }
43 flag.Parse()
44- if *revFile != "" {
45- if flag.NArg() != 0 {
46- flag.Usage()
47+ if *revFile != "" || *updateMany {
48+ var files []string
49+ if *revFile != "" {
50+ if flag.NArg() != 0 {
51+ flag.Usage()
52+ }
53+ files = []string{*revFile}
54+ } else {
55+ if flag.NArg() == 0 {
56+ flag.Usage()
57+ }
58+ files = flag.Args()
59 }
60- update(*revFile)
61+ update(files)
62 } else {
63 pkgs := flag.Args()
64 if len(pkgs) == 0 {
65@@ -81,12 +101,17 @@
66 os.Exit(exitCode)
67 }
68
69-func update(file string) {
70- projects, err := parseDepFile(file)
71- if err != nil {
72- errorf("cannot parse %q: %v", file, err)
73- return
74+func update(files []string) {
75+ fp := make(map[string]map[string]*depInfo)
76+ for _, file := range files {
77+ projects, err := parseDepFile(file)
78+ if err != nil {
79+ errorf("cannot parse %q: %v", file, err)
80+ return
81+ }
82+ fp[file] = projects
83 }
84+ projects := reconcileDeps(fp)
85 // First get info on all the projects, make sure their working
86 // directories are all clean and prune out the ones which
87 // don't need updating.
88@@ -117,6 +142,57 @@
89 updateProjects(projects)
90 }
91
92+type fileDepInfo struct {
93+ file string
94+ *depInfo
95+}
96+
97+// reconcileDeps takes dependencies from a bunch
98+// of different dependency files and returns a single
99+// unified project-to-info map containing all the dependencies,
100+// each one at the latest version found.
101+func reconcileDeps(fp map[string]map[string]*depInfo) map[string]*depInfo {
102+ m := make(map[string]byLatestDep)
103+ for file, projects := range fp {
104+ for _, info := range projects {
105+ m[info.project] = append(m[info.project], fileDepInfo{
106+ file: file,
107+ depInfo: info,
108+ })
109+ }
110+ }
111+ r := make(map[string]*depInfo)
112+ for project, infos := range m {
113+ if *latest {
114+ sort.Stable(infos)
115+ }
116+ r[project] = infos[0].depInfo
117+ for _, info := range infos {
118+ if info.revid != infos[0].revid {
119+ fmt.Printf("%s: warning: ignoring %s at %s; using %s from %s\n", info.file, project, info.revno, infos[0].revno, infos[0].file)
120+ }
121+ }
122+ }
123+ return r
124+}
125+
126+// byLatestDep can be used to sort dependency infos
127+// into latest-first order.
128+type byLatestDep []fileDepInfo
129+
130+func (s byLatestDep) Len() int { return len(s) }
131+func (s byLatestDep) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
132+func (s byLatestDep) Less(i, j int) bool {
133+ i0, i1 := s[i], s[j]
134+ if i0.revid != i1.revid {
135+ if i0.vcs.Kind() != i1.vcs.Kind() {
136+ return i0.vcs.Kind() < i1.vcs.Kind()
137+ }
138+ return i0.vcs.Later(i0.depInfo.VCSInfo, i1.depInfo.VCSInfo)
139+ }
140+ return i0.file < i1.file
141+}
142+
143 func updateProjects(projects map[string]*depInfo) {
144 limit := make(chan struct{}, *parallel)
145 type result struct {
146@@ -516,6 +592,7 @@
147 Info(dir string) (VCSInfo, error)
148 Update(dir, revid string) error
149 Fetch(dir string) error
150+ Later(i0, i1 VCSInfo) bool
151 }
152
153 type VCSInfo struct {
154@@ -587,6 +664,24 @@
155 return err
156 }
157
158+func (gitVCS) Later(i0, i1 VCSInfo) bool {
159+ if i0.revno == "" || i1.revno == "" {
160+ return i0.revno != ""
161+ }
162+ t0, err0 := time.Parse(time.RFC3339, i0.revno)
163+ if err0 != nil {
164+ fmt.Printf("warning: cannot parse time stamp %s: %v", i0.revno, err0)
165+ }
166+ t1, err1 := time.Parse(time.RFC3339, i1.revno)
167+ if err1 != nil {
168+ fmt.Printf("warning: cannot parse time stamp %s: %v", i1.revno, err1)
169+ }
170+ if err0 != nil || err1 != nil {
171+ return err0 == nil
172+ }
173+ return t0.After(t1)
174+}
175+
176 type bzrVCS struct{}
177
178 func (bzrVCS) Kind() string {
179@@ -636,6 +731,10 @@
180 return err
181 }
182
183+func (bzrVCS) Later(i0, i1 VCSInfo) bool {
184+ return laterIntRevno(i0, i1)
185+}
186+
187 var validHgInfo = regexp.MustCompile(`^([a-f0-9]+) ([0-9]+)$`)
188
189 type hgVCS struct{}
190@@ -675,9 +774,33 @@
191 return err
192 }
193
194+func (hgVCS) Later(i0, i1 VCSInfo) bool {
195+ return laterIntRevno(i0, i1)
196+}
197+
198+func laterIntRevno(i0, i1 VCSInfo) bool {
199+ if i0.revno == "" || i1.revno == "" {
200+ return i0.revno != ""
201+ }
202+ r0, err0 := strconv.Atoi(i0.revno)
203+ if err0 != nil {
204+ fmt.Printf("warning: cannot parse revision number %s: %v", i0.revno, err0)
205+ }
206+ r1, err1 := strconv.Atoi(i1.revno)
207+ if err1 != nil {
208+ fmt.Printf("warning: cannot parse revision number %s: %v", i1.revno, err1)
209+ }
210+ if err0 != nil || err1 != nil {
211+ return err0 == nil
212+ }
213+ return r0 > r1
214+}
215+
216 func runUpdateCmd(dir string, name string, args ...string) (string, error) {
217 if *dryRun {
218- printShellCommand(dir, name, args)
219+ if *printCommands {
220+ printShellCommand(dir, name, args)
221+ }
222 return "", nil
223 }
224 return runCmd(dir, name, args...)
225@@ -685,9 +808,6 @@
226
227 func runCmd(dir string, name string, args ...string) (string, error) {
228 var outData, errData bytes.Buffer
229- if *printCommands {
230- printShellCommand(dir, name, args)
231- }
232 c := exec.Command(name, args...)
233 c.Stdout = &outData
234 c.Stderr = &errData

Subscribers

People subscribed via source and target branches