Jiri fails when referred tag is not in any branch

If error occures while checking out head, try to fetch tag first before
returning a error

TO-315

Change-Id: I49268e640f4877c49b2bc4b75a4b49ec2c8c1b04
diff --git a/git/git.go b/git/git.go
index 7f0693f..23de266 100644
--- a/git/git.go
+++ b/git/git.go
@@ -90,6 +90,25 @@
 	return g.FetchRefspec(remote, "", opts...)
 }
 
+func (g *Git) CreateLightweightTag(name string) error {
+	repo, err := git2go.OpenRepository(g.rootDir)
+	if err != nil {
+		return err
+	}
+	defer repo.Free()
+	head, err := repo.Head()
+	if err != nil {
+		return err
+	}
+	defer head.Free()
+	c, err := repo.LookupCommit(head.Target())
+	if err != nil {
+		return err
+	}
+	_, err = repo.Tags.CreateLightweight(name, c, false)
+	return err
+}
+
 // FetchRefspec fetches refs and tags from the given remote for a particular refspec.
 func (g *Git) FetchRefspec(remoteName, refspec string, opts ...FetchOpt) error {
 	repo, err := git2go.OpenRepository(g.rootDir)
diff --git a/gitutil/git.go b/gitutil/git.go
index 70b91cf..f83aab0 100644
--- a/gitutil/git.go
+++ b/gitutil/git.go
@@ -490,6 +490,7 @@
 	prune := false
 	updateShallow := false
 	depth := 0
+	fetchTag := ""
 	for _, opt := range opts {
 		switch typedOpt := opt.(type) {
 		case TagsOpt:
@@ -502,6 +503,8 @@
 			depth = int(typedOpt)
 		case UpdateShallowOpt:
 			updateShallow = bool(typedOpt)
+		case FetchTagOpt:
+			fetchTag = string(typedOpt)
 		}
 	}
 	args := []string{}
@@ -524,6 +527,9 @@
 	if remote != "" {
 		args = append(args, remote)
 	}
+	if fetchTag != "" {
+		args = append(args, "tag", fetchTag)
+	}
 	if refspec != "" {
 		args = append(args, refspec)
 	}
diff --git a/gitutil/options.go b/gitutil/options.go
index e598c29..b01705d 100644
--- a/gitutil/options.go
+++ b/gitutil/options.go
@@ -73,6 +73,10 @@
 
 func (TagsOpt) fetchOpt() {}
 
+type FetchTagOpt string
+
+func (FetchTagOpt) fetchOpt() {}
+
 type AllOpt bool
 
 func (AllOpt) fetchOpt() {}
diff --git a/project/project.go b/project/project.go
index 99d9119..de39df0 100644
--- a/project/project.go
+++ b/project/project.go
@@ -1424,7 +1424,21 @@
 		return err
 	}
 	git := gitutil.New(jirix, gitutil.RootDirOpt(project.Path))
-	return git.CheckoutBranch(revision, gitutil.DetachOpt(true), gitutil.ForceOpt(forceCheckout))
+	err = git.CheckoutBranch(revision, gitutil.DetachOpt(true), gitutil.ForceOpt(forceCheckout))
+	if err == nil {
+		return nil
+	}
+	if project.Revision != "" && project.Revision != "HEAD" {
+		//might be a tag
+		if err2 := fetch(jirix, project.Path, "origin", gitutil.FetchTagOpt(project.Revision)); err2 != nil {
+			// error while fetching tag, return original err and debug log this err
+			jirix.Logger.Debugf("Error while fetching tag for project %s (%s): %s\n\n", project.Name, project.Path, err2)
+			return err
+		} else {
+			return git.CheckoutBranch(revision, gitutil.DetachOpt(true), gitutil.ForceOpt(forceCheckout))
+		}
+	}
+	return err
 }
 
 func tryRebase(jirix *jiri.X, project Project, branch string) (bool, error) {
diff --git a/project/project_test.go b/project/project_test.go
index 0c4c3d6..c9dfc0f 100644
--- a/project/project_test.go
+++ b/project/project_test.go
@@ -1578,6 +1578,62 @@
 	checkJiriRevFiles(t, fake.X, localProjects[1])
 }
 
+func TestTagNotContainedInBranch(t *testing.T) {
+	localProjects, fake, cleanup := setupUniverse(t)
+	defer cleanup()
+	if err := fake.UpdateUniverse(false); err != nil {
+		t.Fatal(err)
+	}
+
+	gitRemote := gitutil.New(fake.X, gitutil.UserNameOpt("John Doe"), gitutil.UserEmailOpt("john.doe@example.com"), gitutil.RootDirOpt(fake.Projects[localProjects[1].Name]))
+	gr := git.NewGit(fake.Projects[localProjects[1].Name])
+	if err := gitRemote.CreateAndCheckoutBranch("non-master"); err != nil {
+		t.Fatal(err)
+	}
+
+	// Create commits in remote repo
+	writeReadme(t, fake.X, fake.Projects[localProjects[1].Name], "non-master commit")
+	writeFile(t, fake.X, fake.Projects[localProjects[1].Name], "file1", "file1")
+	file1CommitRev, _ := gr.CurrentRevision()
+	if err := gr.CreateLightweightTag("testtag"); err != nil {
+		t.Fatalf("Creating tag: %s", err)
+
+	}
+	if err := gitRemote.CheckoutBranch("master"); err != nil {
+		t.Fatal(err)
+	}
+	if err := gitRemote.DeleteBranch("non-master", gitutil.ForceOpt(true)); err != nil {
+		t.Fatal(err)
+	}
+
+	m, err := fake.ReadRemoteManifest()
+	if err != nil {
+		t.Fatal(err)
+	}
+	projects := []project.Project{}
+	for _, p := range m.Projects {
+		if p.Name == localProjects[1].Name {
+			p.Revision = "testtag"
+		}
+		projects = append(projects, p)
+	}
+	m.Projects = projects
+	if err := fake.WriteRemoteManifest(m); err != nil {
+		t.Fatal(err)
+	}
+
+	if err := fake.UpdateUniverse(false); err != nil {
+		t.Fatal(err)
+	}
+
+	gl := git.NewGit(localProjects[1].Path)
+	// It rebased properly and pulled latest changes
+	localRev, _ := gl.CurrentRevision()
+	if file1CommitRev != localRev {
+		t.Fatalf("Current commit is %v, it should be %v\n", localRev, file1CommitRev)
+	}
+}
+
 // TestCheckoutSnapshotUrl tests checking out snapshot functionality from a url
 func TestCheckoutSnapshotUrl(t *testing.T) {
 	testCheckoutSnapshot(t, true)