Add -sync option to slothfs-populate.
This option creates a workspace from a freshly dereferenced manifest,
and populates the checkout with symlinks to the checkout.
Change-Id: I0e8d63f5bb05e4fe1a066627c2a7b88920e4300e
diff --git a/cmd/slothfs-deref-manifest/main.go b/cmd/slothfs-deref-manifest/main.go
index c58b508..6d3e1e5 100644
--- a/cmd/slothfs-deref-manifest/main.go
+++ b/cmd/slothfs-deref-manifest/main.go
@@ -16,14 +16,11 @@
import (
"flag"
- "fmt"
"log"
"os"
"github.com/google/slothfs/gitiles"
- "github.com/google/slothfs/manifest"
-
- git "github.com/libgit2/git2go"
+ "github.com/google/slothfs/populate"
)
func main() {
@@ -37,15 +34,15 @@
log.Fatalf("NewService: %v", err)
}
- mf, err := fetchManifest(service, *repo, *branch)
+ mf, err := populate.FetchManifest(service, *repo, *branch)
if err != nil {
- log.Fatalf("fetchManifest: %v", err)
+ log.Fatalf("FetchManifest: %v", err)
}
mf.Filter()
- if err := derefManifest(service, *repo, mf); err != nil {
- log.Fatalf("derefManifest: %v", err)
+ if err := populate.DerefManifest(service, mf); err != nil {
+ log.Fatalf("DerefManifest: %v", err)
}
xml, err := mf.MarshalXML()
@@ -55,63 +52,3 @@
os.Stdout.Write(xml)
}
-
-func fetchManifest(service *gitiles.Service, repo, branch string) (*manifest.Manifest, error) {
- project := service.NewRepoService(repo)
-
- // When checking this out, it's called "manifest.xml". Go figure.
- c, err := project.GetBlob(branch, "default.xml")
- if err != nil {
- return nil, err
- }
- mf, err := manifest.Parse(c)
- if err != nil {
- return nil, err
- }
-
- return mf, nil
-}
-
-func derefManifest(service *gitiles.Service, manifestRepo string, mf *manifest.Manifest) error {
- branchSet := map[string]struct{}{}
-
- var todoProjects []int
- for i, p := range mf.Project {
- rev := mf.ProjectRevision(&p)
- if _, err := git.NewOid(rev); err == nil {
- continue
- }
-
- branchSet[rev] = struct{}{}
- todoProjects = append(todoProjects, i)
- }
-
- var branches []string
- for k := range branchSet {
- branches = append(branches, k)
- }
-
- repos, err := service.List(branches)
- if err != nil {
- return err
- }
- for _, i := range todoProjects {
- p := &mf.Project[i]
-
- proj, ok := repos[p.Name]
- if !ok {
- return fmt.Errorf("server list doesn't mention repo %s", p.Name)
- }
-
- p.CloneURL = proj.CloneURL
-
- branch := mf.ProjectRevision(p)
- commit, ok := proj.Branches[branch]
- if !ok {
- return fmt.Errorf("branch %q for repo %s not returned", branch, p.Name)
- }
-
- p.Revision = commit
- }
- return nil
-}
diff --git a/cmd/slothfs-populate/main.go b/cmd/slothfs-populate/main.go
index b7fcedb..ce50744 100644
--- a/cmd/slothfs-populate/main.go
+++ b/cmd/slothfs-populate/main.go
@@ -15,16 +15,88 @@
package main
import (
+ "bufio"
"flag"
+ "io/ioutil"
"log"
"os"
+ "path/filepath"
+ "strings"
"time"
+ "github.com/google/slothfs/gitiles"
"github.com/google/slothfs/populate"
)
+// findSlothFSMount guesses where slothfs might be mounted.
+func findSlothFSMount() string {
+ f, err := os.Open("/proc/mounts")
+ if err != nil {
+ // We're probably on OSX.
+ return ""
+ }
+ defer f.Close()
+
+ scanner := bufio.NewScanner(f)
+ for scanner.Scan() {
+ line := scanner.Text()
+ fields := strings.Split(line, " ")
+ if len(fields) >= 3 && fields[2] == "fuse.slothfs" {
+ return fields[1]
+ }
+ }
+ return ""
+}
+
+// syncManifest fetches a manifest file, and configures a workspace
+// for it.
+func syncManifest(opts *gitiles.Options, mountPoint, repo, branch string) (string, error) {
+ service, err := gitiles.NewService(*opts)
+ if err != nil {
+ return "", err
+ }
+
+ mf, err := populate.FetchManifest(service, repo, branch)
+ if err != nil {
+ return "", err
+ }
+
+ mf.Filter()
+
+ if err := populate.DerefManifest(service, mf); err != nil {
+ return "", err
+ }
+
+ xml, err := ioutil.TempFile("", "")
+ if err != nil {
+ return "", err
+ }
+
+ xmlBytes, err := mf.MarshalXML()
+ if err != nil {
+ return "", err
+ }
+ if err := ioutil.WriteFile(xml.Name(), xmlBytes, 0644); err != nil {
+ return "", err
+ }
+
+ name := strings.Replace(time.Now().Format("S"+time.RFC3339), ":", "_", -1)
+
+ log.Printf("fetched manifest; configuring workspace %s", name)
+ if err := os.Symlink(xml.Name(), filepath.Join(mountPoint, "config", name)); err != nil {
+ return "", err
+ }
+
+ return filepath.Join(mountPoint, name), nil
+}
+
func main() {
- mount := flag.String("ro", "", "path to slothfs-multifs mount.")
+ gitilesOptions := gitiles.DefineFlags()
+ newROWorkspace := flag.String("ro", "", "Set path to slothfs-multifs mount.")
+ mount := flag.String("mount", "", "Set slothfs mountpoint for -sync option. Autodetected if empty.")
+ sync := flag.Bool("sync", false, "Sync checkout to latest manifest version.")
+ syncBranch := flag.String("sync_branch", "master", "Use this branch for -sync.")
+ syncRepo := flag.String("sync_repo", "platform/manifest", "Use this repo for -sync.")
flag.Parse()
dir := "."
@@ -34,7 +106,28 @@
log.Fatal("too many arguments.")
}
- added, changed, err := populate.Checkout(*mount, dir)
+ if *sync {
+ if *mount == "" {
+ *mount = findSlothFSMount()
+ if *mount == "" {
+ log.Fatal("could not autodetect mount point. Pass --mount option.")
+ }
+ }
+
+ var err error
+ *newROWorkspace, err = syncManifest(gitilesOptions, *mount, *syncRepo, *syncBranch)
+ if err != nil {
+ log.Fatalf("syncManifest: %v", err)
+ }
+ }
+
+ if *newROWorkspace == "" {
+ log.Fatalf("no readonly checkout given. Specify -ro DIR or -sync.")
+ }
+
+ log.Printf("creating symlinks to %s", *newROWorkspace)
+
+ added, changed, err := populate.Checkout(*newROWorkspace, dir)
if err != nil {
log.Fatalf("populate.Checkout: %v", err)
}
diff --git a/docs/manual.md b/docs/manual.md
index 6791204..4c1f17e 100644
--- a/docs/manual.md
+++ b/docs/manual.md
@@ -106,6 +106,9 @@
This populate the `checkout` directory with symlinks into `/slothfs`, yielding a
full check out.
+If there were symlinks to a previous checkout in the workspace, this will also
+update timestamps to make incremental builds work.
+
Syncing
=======
@@ -113,14 +116,11 @@
Advancing your checkout to a different timestamp uses the same commands. To sync
to the current state of the Android tree, do the following
- SYNC=$(date -Iminutes)
- slothfs-deref-manifest > /tmp/${SYNC}.xml
- ln -s /tmp/${SYNC}.xml /slothfs/config/${SYNC}
- slothfs-populate -ro /slothfs/${SYNC} .
+ slothfs-populate -sync .
-this will reroute symlinks in your check to your newly created
-workspace. `slothfs-populate` will also update timestamps so incremental builds
-keep working.
+This fetches the latest manifest file, finds the project revisions, sets up a
+workspace for the manifest, and updates the symlinks from your read/write
+checkout.
Removing a workspace
diff --git a/populate/deref.go b/populate/deref.go
new file mode 100644
index 0000000..7e82553
--- /dev/null
+++ b/populate/deref.go
@@ -0,0 +1,96 @@
+// Copyright 2016 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package populate
+
+import (
+ "fmt"
+
+ "github.com/google/slothfs/gitiles"
+ "github.com/google/slothfs/manifest"
+
+ git "github.com/libgit2/git2go"
+)
+
+// FetchManifest gets the default manifest file from a Gitiles server.
+func FetchManifest(service *gitiles.Service, repo, branch string) (*manifest.Manifest, error) {
+ project := service.NewRepoService(repo)
+
+ // When checking this out, it's called "manifest.xml". Go figure.
+ c, err := project.GetBlob(branch, "default.xml")
+ if err != nil {
+ return nil, err
+ }
+ mf, err := manifest.Parse(c)
+ if err != nil {
+ return nil, err
+ }
+
+ return mf, nil
+}
+
+// DerefManifest uses the Gitiles JSON interface to fill in
+// Project.Revision and Project.CloneURL in the given manifest.
+func DerefManifest(service *gitiles.Service, mf *manifest.Manifest) error {
+ // Collect all branch names we might care about, so we can
+ // request data from all branches in one JSON call. Normally,
+ // all projects use the same branch, but individual projects
+ // may specify a special branch.
+ branchSet := map[string]struct{}{}
+
+ var todoProjects []int
+ for i, p := range mf.Project {
+ rev := mf.ProjectRevision(&p)
+
+ // According to the repo doc, the revision should be a branch,
+ // either like 'refs/heads/master' or 'master'. We abuse this field by
+ // also allowing commit SHA1s.
+ if _, err := git.NewOid(rev); err == nil {
+ // Already a SHA1, don't change.
+ continue
+ }
+
+ branchSet[rev] = struct{}{}
+ todoProjects = append(todoProjects, i)
+ }
+
+ var branches []string
+ for k := range branchSet {
+ branches = append(branches, k)
+ }
+
+ repos, err := service.List(branches)
+ if err != nil {
+ return err
+ }
+ for _, i := range todoProjects {
+ p := &mf.Project[i]
+
+ proj, ok := repos[p.Name]
+ if !ok {
+ return fmt.Errorf("server list doesn't mention repo %s", p.Name)
+ }
+
+ p.CloneURL = proj.CloneURL
+
+ branch := mf.ProjectRevision(p)
+ commit, ok := proj.Branches[branch]
+ if !ok {
+ return fmt.Errorf("branch %q for repo %s not returned", branch, p.Name)
+ }
+
+ p.Revision = commit
+ }
+ return nil
+}