Change source manifest data structure

Change-Id: Ic1cc981ba15e2961b845233de350fcf5fe8566ae
diff --git a/cmd/jiri/snapshot_test.go b/cmd/jiri/snapshot_test.go
index 5df119f..7ba0606 100644
--- a/cmd/jiri/snapshot_test.go
+++ b/cmd/jiri/snapshot_test.go
@@ -176,10 +176,10 @@
 	for i := 0; i < numProjects; i++ {
 		paths = append(paths, localProjectName(i))
 	}
-	revMap := make(map[string]string)
+	revMap := make(map[string][]byte)
 	for _, path := range paths {
 		g := git.NewGit(filepath.Join(fake.X.Root, path))
-		if rev, err := g.CurrentRevision(); err != nil {
+		if rev, err := g.CurrentRevisionRaw(); err != nil {
 			t.Fatal(err)
 		} else {
 			revMap[path] = rev
@@ -211,22 +211,27 @@
 	sm := &project.SourceManifest{
 		Version: project.SourceManifestVersion,
 	}
-	sm.Checkouts = append(sm.Checkouts, project.SourceCheckout{
-		LocalPath: "manifest",
-		RepoURL:   fake.Projects["manifest"],
-		SCMType:   project.GIT,
-		Revision:  revMap["manifest"],
-	})
-	for i := 0; i < numProjects; i++ {
-		sc := project.SourceCheckout{
-			SCMType:   project.GIT,
-			LocalPath: localProjectName(i),
-			RepoURL:   fake.Projects[remoteProjectName(i)],
-			Revision:  revMap[localProjectName(i)],
-		}
-		sm.Checkouts = append(sm.Checkouts, sc)
+	sm.Directories = make(map[string]*project.SourceManifest_Directory)
+	sm.Directories["manifest"] = &project.SourceManifest_Directory{
+		GitCheckout: &project.SourceManifest_GitCheckout{
+			RepoUrl:     fake.Projects["manifest"],
+			Revision:    revMap["manifest"],
+			TrackingRef: "refs/heads/master",
+		},
 	}
-	sm.Checkouts[3].TrackingRef = "refs/heads/test-branch"
+	for i := 0; i < numProjects; i++ {
+		ref := "refs/heads/master"
+		if i == 2 {
+			ref = "refs/heads/test-branch"
+		}
+		sm.Directories[localProjectName(i)] = &project.SourceManifest_Directory{
+			GitCheckout: &project.SourceManifest_GitCheckout{
+				RepoUrl:     fake.Projects[remoteProjectName(i)],
+				Revision:    revMap[localProjectName(i)],
+				TrackingRef: ref,
+			},
+		}
+	}
 
 	want, err := json.MarshalIndent(sm, "", "  ")
 	if err != nil {
diff --git a/git/git.go b/git/git.go
index 4d43d34..7f0693f 100644
--- a/git/git.go
+++ b/git/git.go
@@ -6,6 +6,7 @@
 
 import (
 	"fmt"
+
 	git2go "github.com/libgit2/git2go"
 )
 
@@ -19,6 +20,20 @@
 	}
 }
 
+func (g *Git) CurrentRevisionRaw() ([]byte, error) {
+	repo, err := git2go.OpenRepository(g.rootDir)
+	if err != nil {
+		return nil, err
+	}
+	defer repo.Free()
+	head, err := repo.Head()
+	if err != nil {
+		return nil, err
+	}
+	defer head.Free()
+	return head.Target()[:], nil
+}
+
 func (g *Git) CurrentRevision() (string, error) {
 	repo, err := git2go.OpenRepository(g.rootDir)
 	if err != nil {
diff --git a/project/source_manifest.go b/project/source_manifest.go
index 69acc75..2515a8d 100644
--- a/project/source_manifest.go
+++ b/project/source_manifest.go
@@ -13,26 +13,67 @@
 	"sort"
 
 	"fuchsia.googlesource.com/jiri"
+	"fuchsia.googlesource.com/jiri/git"
 )
 
-type SCMType int16
-
 const (
-	GIT                   = SCMType(0)
-	SourceManifestVersion = 1
+	SourceManifestVersion = int32(0)
 )
 
-type SourceCheckout struct {
-	SCMType     SCMType `json:"scm_type"`
-	RepoURL     string  `json:"repo_url"`
-	Revision    string  `json:"revision"`
-	LocalPath   string  `json:"local_path"`
-	TrackingRef string  `json:"tracking_ref,omitempty"`
+type SourceManifest_GitCheckout struct {
+	// The canonicalized URL of the original repo that is considered the “source
+	// of truth” for the source code. Ex.
+	//   https://chromium.googlesource.com/chromium/tools/build.git
+	//   https://github.com/luci/recipes-py
+	RepoUrl string `json:"repo_url,omitempty"`
+
+	// If different from repo_url, this can be the URL of the repo that the source
+	// was actually fetched from (i.e. a mirror). Ex.
+	//   https://chromium.googlesource.com/external/github.com/luci/recipes-py
+	//
+	// If this is empty, it's presumed to be equal to repo_url.
+	FetchUrl string `json:"fetch_url,omitempty"`
+
+	// The fully resolved revision (commit hash) of the source. Ex.
+	//   3617b0eea7ec74b8e731a23fed2f4070cbc284c4
+	//
+	// Note that this is the raw revision bytes, not their hex-encoded form.
+	Revision []byte `json:"revision,omitempty"`
+
+	// The ref that the task used to resolve the revision of the source (if any). Ex.
+	//   refs/heads/master
+	//   refs/changes/04/511804/4
+	//
+	// This should always be a ref on the hosted repo (not any local alias
+	// like 'refs/remotes/...').
+	//
+	// This should always be an absolute ref (i.e. starts with 'refs/'). An
+	// example of a non-absolute ref would be 'master'.
+	TrackingRef string `json:"tracking_ref,omitempty"`
+}
+
+type SourceManifest_Directory struct {
+	GitCheckout *SourceManifest_GitCheckout `json:"git_checkout,omitempty"`
 }
 
 type SourceManifest struct {
-	Version   int              `json:"version"`
-	Checkouts []SourceCheckout `json:"checkouts"`
+	// Version will increment on backwards-incompatible changes only. Backwards
+	// compatible changes will not alter this version number.
+	//
+	// Currently, the only valid version number is 0.
+	Version int32 `json:"version"`
+
+	// Map of local file system directory path (with forward slashes) to
+	// a Directory message containing one or more deployments.
+	//
+	// The local path is relative to some job-specific root. This should be used
+	// for informational/display/organization purposes, and should not be used as
+	// a global primary key. i.e. if you depend on chromium/src.git being in
+	// a folder called “src”, I will find you and make really angry faces at you
+	// until you change it...(╬ಠ益ಠ). Instead, implementations should consider
+	// indexing by e.g. git repository URL or cipd package name as more better
+	// primary keys.
+	Directories map[string]*SourceManifest_Directory `json:"directories"`
 }
 
 func NewSourceManifest(jirix *jiri.X, projects Projects) (*SourceManifest, error) {
@@ -46,21 +87,25 @@
 		i++
 	}
 	sm := &SourceManifest{
-		Version:   SourceManifestVersion,
-		Checkouts: make([]SourceCheckout, len(p)),
+		Version:     SourceManifestVersion,
+		Directories: make(map[string]*SourceManifest_Directory),
 	}
 	sort.Sort(ProjectsByPath(p))
-	for i, proj := range p {
-		sc := SourceCheckout{
-			SCMType:   GIT,
-			RepoURL:   proj.Remote,
-			Revision:  proj.Revision,
-			LocalPath: proj.Path,
+	for _, proj := range p {
+		gc := &SourceManifest_GitCheckout{
+			RepoUrl: proj.Remote,
 		}
-		if proj.RemoteBranch != "" && proj.RemoteBranch != "master" {
-			sc.TrackingRef = "refs/heads/" + proj.RemoteBranch
+		g := git.NewGit(filepath.Join(jirix.Root, proj.Path))
+		if rev, err := g.CurrentRevisionRaw(); err != nil {
+			return nil, err
+		} else {
+			gc.Revision = rev
 		}
-		sm.Checkouts[i] = sc
+		if proj.RemoteBranch == "" {
+			proj.RemoteBranch = "master"
+		}
+		gc.TrackingRef = "refs/heads/" + proj.RemoteBranch
+		sm.Directories[proj.Path] = &SourceManifest_Directory{GitCheckout: gc}
 	}
 	return sm, nil
 }