You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mynewt.apache.org by ma...@apache.org on 2017/03/07 23:49:46 UTC

[1/3] incubator-mynewt-newt git commit: MYNEWT-495: updates to sync command

Repository: incubator-mynewt-newt
Updated Branches:
  refs/heads/develop cd6aa05ad -> 737da68dd


MYNEWT-495: updates to sync command

- When repo is already cloned, try to update from remote
- If -f is passed, create a modifications diff, and try harder
  to update current local (stash, clean, checkout, pull)


Project: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/commit/0196ba53
Tree: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/tree/0196ba53
Diff: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/diff/0196ba53

Branch: refs/heads/develop
Commit: 0196ba5338aa5efdd8110eb1012e17104e435616
Parents: 7ecee8b
Author: Fabio Utzig <ut...@utzig.org>
Authored: Fri Mar 3 09:49:52 2017 -0300
Committer: Fabio Utzig <ut...@utzig.org>
Committed: Mon Mar 6 09:42:46 2017 -0300

----------------------------------------------------------------------
 newt/cli/project_cmds.go                        |  23 ++-
 newt/downloader/downloader.go                   | 110 ++++++++++---
 newt/repo/repo.go                               | 157 +++++++++++++++++--
 newt/vendor/mynewt.apache.org/newt/util/util.go |   1 -
 4 files changed, 247 insertions(+), 44 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/0196ba53/newt/cli/project_cmds.go
----------------------------------------------------------------------
diff --git a/newt/cli/project_cmds.go b/newt/cli/project_cmds.go
index a8d93b2..89a6eae 100644
--- a/newt/cli/project_cmds.go
+++ b/newt/cli/project_cmds.go
@@ -20,6 +20,7 @@
 package cli
 
 import (
+	"fmt"
 	"os"
 	"sort"
 	"strings"
@@ -156,26 +157,32 @@ func syncRunCmd(cmd *cobra.Command, args []string) {
 		NewtUsage(nil, err)
 	}
 
+	var failedRepos []string
 	for _, repo := range repos {
 		var exists bool
+		var updated bool
 		if repo.IsLocal() {
 			continue
 		}
 		vers := ps.GetInstalledVersion(repo.Name())
 		if vers == nil {
 			util.StatusMessage(util.VERBOSITY_DEFAULT,
-				"No installed version of %s found, skipping\n",
+				"No installed version of %s found, skipping\n\n",
 				repo.Name())
 		}
-		if err, exists = repo.Sync(vers, newtutil.NewtForce); err != nil {
-			NewtUsage(nil, err)
+		exists, updated, err = repo.Sync(vers, newtutil.NewtForce)
+		if exists && !updated {
+			failedRepos = append(failedRepos, repo.Name())
 		}
-
-		if exists && !newtutil.NewtForce {
-			util.StatusMessage(util.VERBOSITY_DEFAULT,
-				"Skipping resync of %s because directory exists.  To "+
-					"force resync, add the -f (force) option.\n", repo.Name())
+	}
+	if len(failedRepos) > 0 {
+		var forceMsg string
+		if !newtutil.NewtForce {
+			forceMsg = " To force resync, add the -f (force) option."
 		}
+		err = util.NewNewtError(fmt.Sprintf("Failed for repos: %v."+
+			forceMsg, failedRepos))
+		NewtUsage(nil, err)
 	}
 }
 

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/0196ba53/newt/downloader/downloader.go
----------------------------------------------------------------------
diff --git a/newt/downloader/downloader.go b/newt/downloader/downloader.go
index 0ab201e..e57ecfa 100644
--- a/newt/downloader/downloader.go
+++ b/newt/downloader/downloader.go
@@ -27,6 +27,7 @@ import (
 	"os"
 	"os/exec"
 	"path/filepath"
+	"strings"
 
 	log "github.com/Sirupsen/logrus"
 
@@ -39,6 +40,10 @@ type Downloader interface {
 	Branch() string
 	SetBranch(branch string)
 	DownloadRepo(branch string) (string, error)
+	CurrentBranch(path string) (string, error)
+	UpdateRepo(path string) error
+	CleanupRepo(path string, branchName string) error
+	LocalDiff(path string) ([]byte, error)
 }
 
 type GenericDownloader struct {
@@ -66,42 +71,57 @@ type LocalDownloader struct {
 	Path string
 }
 
-func checkout(repoDir string, commit string) error {
-	// Retrieve the current directory so that we can get back to where we
-	// started after the download completes.
-	pwd, err := os.Getwd()
+func executeGitCommand(dir string, cmd []string) ([]byte, error) {
+	wd, err := os.Getwd()
 	if err != nil {
-		return util.NewNewtError(err.Error())
+		return nil, util.NewNewtError(err.Error())
 	}
 
 	gitPath, err := exec.LookPath("git")
 	if err != nil {
-		return util.NewNewtError(fmt.Sprintf("Can't find git binary: %s\n",
+		return nil, util.NewNewtError(fmt.Sprintf("Can't find git binary: %s\n",
 			err.Error()))
 	}
 	gitPath = filepath.ToSlash(gitPath)
 
-	if err := os.Chdir(repoDir); err != nil {
-		return util.NewNewtError(err.Error())
+	if err := os.Chdir(dir); err != nil {
+		return nil, util.NewNewtError(err.Error())
 	}
 
-	// Checkout the specified commit.
+	defer os.Chdir(wd)
+
+	gitCmd := []string{gitPath}
+	gitCmd = append(gitCmd, cmd...)
+	output, err := util.ShellCommand(gitCmd, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	return output, nil
+}
+
+func checkout(repoDir string, commit string) error {
 	cmd := []string{
-		gitPath,
 		"checkout",
 		commit,
 	}
+	_, err := executeGitCommand(repoDir, cmd)
+	return err
+}
 
-	if o, err := util.ShellCommand(cmd, nil); err != nil {
-		return util.NewNewtError(string(o))
-	}
+func pull(repoDir string) error {
+	_, err := executeGitCommand(repoDir, []string{"pull"})
+	return err
+}
 
-	// Go back to original directory.
-	if err := os.Chdir(pwd); err != nil {
-		return util.NewNewtError(err.Error())
-	}
+func stash(repoDir string) error {
+	_, err := executeGitCommand(repoDir, []string{"stash"})
+	return err
+}
 
-	return nil
+func clean(repoDir string) error {
+	_, err := executeGitCommand(repoDir, []string{"clean", "-f"})
+	return err
 }
 
 func (gd *GenericDownloader) Branch() string {
@@ -172,6 +192,39 @@ func (gd *GithubDownloader) FetchFile(name string, dest string) error {
 	return nil
 }
 
+func (gd *GithubDownloader) CurrentBranch(path string) (string, error) {
+	cmd := []string{"rev-parse", "--abbrev-ref", "HEAD"}
+	branch, err := executeGitCommand(path, cmd)
+	return strings.Trim(string(branch), "\r\n"), err
+}
+
+func (gd *GithubDownloader) UpdateRepo(path string) error {
+	return pull(path)
+}
+
+func (gd *GithubDownloader) CleanupRepo(path string, branchName string) error {
+	err := stash(path)
+	if err != nil {
+		return err
+	}
+
+	err = clean(path)
+	if err != nil {
+		return err
+	}
+
+	err = checkout(path, branchName)
+	if err != nil {
+		return err
+	}
+
+	return pull(path)
+}
+
+func (gd *GithubDownloader) LocalDiff(path string) ([]byte, error) {
+	return executeGitCommand(path, []string{"diff"})
+}
+
 func (gd *GithubDownloader) DownloadRepo(commit string) (string, error) {
 	// Get a temporary directory, and copy the repository into that directory.
 	tmpdir, err := ioutil.TempDir("", "newt-repo")
@@ -243,6 +296,27 @@ func (ld *LocalDownloader) FetchFile(name string, dest string) error {
 	return nil
 }
 
+func (ld *LocalDownloader) CurrentBranch(path string) (string, error) {
+	cmd := []string{"rev-parse", "--abbrev-ref", "HEAD"}
+	branch, err := executeGitCommand(path, cmd)
+	return strings.Trim(string(branch), "\r\n"), err
+}
+
+// NOTE: intentionally always error...
+func (ld *LocalDownloader) UpdateRepo(path string) error {
+	return util.NewNewtError(fmt.Sprintf("Can't pull from a local repo\n"))
+}
+
+func (ld *LocalDownloader) CleanupRepo(path string, branchName string) error {
+	os.RemoveAll(path)
+	_, err := ld.DownloadRepo(branchName)
+	return err
+}
+
+func (ld *LocalDownloader) LocalDiff(path string) ([]byte, error) {
+	return executeGitCommand(path, []string{"diff"})
+}
+
 func (ld *LocalDownloader) DownloadRepo(commit string) (string, error) {
 	// Get a temporary directory, and copy the repository into that directory.
 	tmpdir, err := ioutil.TempDir("", "newt-repo")

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/0196ba53/newt/repo/repo.go
----------------------------------------------------------------------
diff --git a/newt/repo/repo.go b/newt/repo/repo.go
index a2331f7..a4a916f 100644
--- a/newt/repo/repo.go
+++ b/newt/repo/repo.go
@@ -25,6 +25,7 @@ import (
 	"os"
 	"path/filepath"
 	"strings"
+	"time"
 
 	log "github.com/Sirupsen/logrus"
 	"github.com/spf13/cast"
@@ -347,6 +348,11 @@ func (r *Repo) repoFilePath() string {
 		".configs/" + r.name + "/"
 }
 
+func (r *Repo) patchesFilePath() string {
+	return interfaces.GetProject().Path() + "/" + REPOS_DIR +
+		"/.patches/"
+}
+
 func (r *Repo) downloadRepo(branchName string) error {
 	dl := r.downloader
 
@@ -368,6 +374,66 @@ func (r *Repo) downloadRepo(branchName string) error {
 	return nil
 }
 
+func (r *Repo) checkExists() bool {
+	return util.NodeExist(r.Path())
+}
+
+func (r *Repo) updateRepo() error {
+	dl := r.downloader
+	err := dl.UpdateRepo(r.Path())
+	if err != nil {
+		return util.NewNewtError(fmt.Sprintf("\tError updating\n"))
+	}
+	return nil
+}
+
+func (r *Repo) cleanupRepo(branchName string) error {
+	dl := r.downloader
+	err := dl.CleanupRepo(r.Path(), branchName)
+	if err != nil {
+		return util.NewNewtError(fmt.Sprintf("\tError cleaning and updating\n"))
+	}
+	return nil
+}
+
+func (r *Repo) saveLocalDiff() (string, error) {
+	dl := r.downloader
+	diff, err := dl.LocalDiff(r.Path())
+	if err != nil {
+		return "", util.NewNewtError(fmt.Sprintf(
+			"Error creating diff for \"%s\" : %s", r.Name(), err.Error()))
+	}
+
+	// NOTE: date was not a typo: https://golang.org/pkg/time/#Time.Format
+	timenow := time.Now().Format("20060102_150405")
+	filename := r.patchesFilePath() + r.Name() + "_" + timenow + ".diff"
+
+	f, err := os.Create(filename)
+	if err != nil {
+		return "", util.NewNewtError(fmt.Sprintf(
+			"Error creating repo diff file \"%s\"", filename))
+	}
+	defer f.Close()
+
+	_, err = f.Write(diff)
+	if err != nil {
+		return "", util.NewNewtError(fmt.Sprintf(
+			"Error writing repo diff file \"%s\"", filename))
+	}
+
+	return filename, nil
+}
+
+func (r *Repo) currentBranch() (string, error) {
+	dl := r.downloader
+	branch, err := dl.CurrentBranch(r.Path())
+	if err != nil {
+		return "", util.NewNewtError(fmt.Sprintf("Error finding current branch for \"%s\" : %s",
+			r.Name(), err.Error()))
+	}
+	return branch, nil
+}
+
 func (r *Repo) checkForceInstall(force bool) (error, bool) {
 	// Copy the git repo into /repos/, error'ing out if the repo already exists
 	if util.NodeExist(r.Path()) {
@@ -407,33 +473,82 @@ func (r *Repo) Install(force bool) (*Version, error) {
 	return vers, nil
 }
 
-func (r *Repo) Sync(vers *Version, force bool) (error, bool) {
+func (r *Repo) Sync(vers *Version, force bool) (bool, bool, error) {
 	var exists bool
 	var err error
+	var currBranch string
 
-	if err, exists = r.checkForceInstall(force); err != nil {
-		return err, exists
-	}
-	if exists && !force {
-		return nil, exists
-	}
+	exists = r.checkExists()
 
 	// Update the repo description
 	if _, updated, err := r.UpdateDesc(); updated != true || err != nil {
-		return util.NewNewtError("Cannot update repository description."), exists
+		return exists, false, util.NewNewtError("Cannot update repository description.")
 	}
 
-	branchName, vers, found := r.rdesc.MatchVersion(vers)
+	branchName, _, found := r.rdesc.MatchVersion(vers)
 	if found == false {
-		return util.NewNewtError(fmt.Sprintf("Branch description for %s not found",
-			r.Name())), exists
+		return exists, false, util.NewNewtError(fmt.Sprintf(
+			"Branch description for %s not found", r.Name()))
 	}
 
-	if err := r.downloadRepo(branchName); err != nil {
-		return err, exists
+	if exists {
+		// Here assuming that if the branch was changed by the user,
+		// the user must know what he's doing...
+		// but, if -f is passed let's just save the work and re-clone
+		currBranch, err = r.currentBranch()
+		if err != nil {
+			return exists, false, err
+		} else if currBranch != branchName {
+			msg := "Unexpected local branch for %s: \"%s\" != \"%s\""
+			if force {
+				util.StatusMessage(util.VERBOSITY_VERBOSE,
+					msg, r.rdesc.name, currBranch, branchName)
+			} else {
+				err = util.NewNewtError(
+					fmt.Sprintf(msg, r.rdesc.name, currBranch, branchName))
+				return exists, false, err
+			}
+		}
+
+		// Don't try updating if on an invalid branch...
+		if currBranch == branchName {
+			util.StatusMessage(util.VERBOSITY_VERBOSE, "\tTrying to update repository... ")
+			err = r.updateRepo()
+			if err == nil {
+				util.StatusMessage(util.VERBOSITY_VERBOSE, " success!\n")
+				return exists, true, err
+			} else {
+				util.StatusMessage(util.VERBOSITY_VERBOSE, " failed!\n")
+				if !force {
+					return exists, false, err
+				}
+			}
+		}
+
+		filename, err := r.saveLocalDiff()
+		if err != nil {
+			return exists, false, err
+		}
+		wd, _ := os.Getwd()
+		filename, _ = filepath.Rel(wd, filename)
+
+		util.StatusMessage(util.VERBOSITY_DEFAULT, "Saved local diff: "+
+			"\"%s\"\n", filename)
+
+		err = r.cleanupRepo(branchName)
+		if err != nil {
+			return exists, false, err
+		}
+
+	} else {
+		// fresh or updating was unsuccessfull and force was given...
+		err = r.downloadRepo(branchName)
+		if err != nil {
+			return exists, false, err
+		}
 	}
 
-	return nil, exists
+	return exists, true, nil
 }
 
 func (r *Repo) UpdateDesc() ([]*Repo, bool, error) {
@@ -443,7 +558,7 @@ func (r *Repo) UpdateDesc() ([]*Repo, bool, error) {
 		return nil, false, nil
 	}
 
-	util.StatusMessage(util.VERBOSITY_DEFAULT, "%s\n", r.Name())
+	util.StatusMessage(util.VERBOSITY_VERBOSE, "[%s]:\n", r.Name())
 
 	if err = r.DownloadDesc(); err != nil {
 		return nil, false, err
@@ -463,8 +578,8 @@ func (r *Repo) UpdateDesc() ([]*Repo, bool, error) {
 func (r *Repo) DownloadDesc() error {
 	dl := r.downloader
 
-	util.StatusMessage(util.VERBOSITY_VERBOSE, "Downloading "+
-		"repository description for %s...\n", r.Name())
+	util.StatusMessage(util.VERBOSITY_VERBOSE, "\tDownloading "+
+		"repository description... ")
 
 	// Configuration path
 	cpath := r.repoFilePath()
@@ -481,6 +596,14 @@ func (r *Repo) DownloadDesc() error {
 		return err
 	}
 
+	// also create a directory to save diffs for sync
+	cpath = r.patchesFilePath()
+	if util.NodeNotExist(cpath) {
+		if err := os.MkdirAll(cpath, REPO_DEFAULT_PERMS); err != nil {
+			return util.NewNewtError(err.Error())
+		}
+	}
+
 	util.StatusMessage(util.VERBOSITY_VERBOSE, " success!\n")
 
 	return nil

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/0196ba53/newt/vendor/mynewt.apache.org/newt/util/util.go
----------------------------------------------------------------------
diff --git a/newt/vendor/mynewt.apache.org/newt/util/util.go b/newt/vendor/mynewt.apache.org/newt/util/util.go
index fa3e60f..083ee9c 100644
--- a/newt/vendor/mynewt.apache.org/newt/util/util.go
+++ b/newt/vendor/mynewt.apache.org/newt/util/util.go
@@ -300,7 +300,6 @@ func ShellCommandLimitDbgOutput(
 	}
 
 	o, err := cmd.CombinedOutput()
-
 	if maxDbgOutputChrs < 0 || len(o) <= maxDbgOutputChrs {
 		dbgStr := string(o)
 		log.Debugf("o=%s", dbgStr)


[3/3] incubator-mynewt-newt git commit: This closes #48.

Posted by ma...@apache.org.
This closes #48.

Merge branch 'sync-refactor' of https://github.com/utzig/incubator-mynewt-newt into develop


Project: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/commit/737da68d
Tree: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/tree/737da68d
Diff: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/diff/737da68d

Branch: refs/heads/develop
Commit: 737da68ddcda814d009902abef8bdfcf1f1c8802
Parents: cd6aa05 0418cea
Author: Marko Kiiskila <ma...@runtime.io>
Authored: Tue Mar 7 15:49:17 2017 -0800
Committer: Marko Kiiskila <ma...@runtime.io>
Committed: Tue Mar 7 15:49:17 2017 -0800

----------------------------------------------------------------------
 newt/cli/project_cmds.go                        |  23 ++-
 newt/downloader/downloader.go                   | 194 ++++++++++++++++--
 newt/repo/repo.go                               | 199 +++++++++++++++----
 newt/vendor/mynewt.apache.org/newt/util/util.go |   1 -
 4 files changed, 351 insertions(+), 66 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/737da68d/newt/repo/repo.go
----------------------------------------------------------------------
diff --cc newt/repo/repo.go
index a2b98c9,139c499..e058466
--- a/newt/repo/repo.go
+++ b/newt/repo/repo.go
@@@ -473,9 -590,9 +588,9 @@@ func (r *Repo) DownloadDesc() error 
  	}
  
  	dl.SetBranch("master")
 -	if err := dl.FetchFile("repository.yml",
 -		cpath+"/"+"repository.yml"); err != nil {
 +	if err := dl.FetchFile(REPO_FILE_NAME,
 +		cpath+"/"+REPO_FILE_NAME); err != nil {
- 		util.StatusMessage(util.VERBOSITY_VERBOSE, " failed\n")
+ 		util.StatusMessage(util.VERBOSITY_VERBOSE, "Download failed\n")
  		return err
  	}
  


[2/3] incubator-mynewt-newt git commit: Updates to sync/upgrade/install inner working

Posted by ma...@apache.org.
Updates to sync/upgrade/install inner working

- sync now creates a new branch when checking out a tag commit
- sync flow now:
  1) stash changes
  2) fetch branchs/tags from origin
  3) merges in changes on master and develop
  4) checks out old branch or new branch (creating if necessary)
  5) stash pops if the previous stash was succesful
- upgrade/install now try first to cleanup and existing repo and if it
  fails do the old style remove+clone.


Project: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/commit/0418ceab
Tree: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/tree/0418ceab
Diff: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/diff/0418ceab

Branch: refs/heads/develop
Commit: 0418ceab007a703ca86652adb7455958d5eac24b
Parents: 0196ba5
Author: Fabio Utzig <ut...@utzig.org>
Authored: Tue Mar 7 11:33:01 2017 -0300
Committer: Fabio Utzig <ut...@utzig.org>
Committed: Tue Mar 7 11:33:01 2017 -0300

----------------------------------------------------------------------
 newt/downloader/downloader.go | 116 +++++++++++++++++++++++++++++++------
 newt/repo/repo.go             |  78 ++++++++++++-------------
 2 files changed, 138 insertions(+), 56 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/0418ceab/newt/downloader/downloader.go
----------------------------------------------------------------------
diff --git a/newt/downloader/downloader.go b/newt/downloader/downloader.go
index e57ecfa..c8e13c6 100644
--- a/newt/downloader/downloader.go
+++ b/newt/downloader/downloader.go
@@ -41,7 +41,7 @@ type Downloader interface {
 	SetBranch(branch string)
 	DownloadRepo(branch string) (string, error)
 	CurrentBranch(path string) (string, error)
-	UpdateRepo(path string) error
+	UpdateRepo(path string, branchName string) error
 	CleanupRepo(path string, branchName string) error
 	LocalDiff(path string) ([]byte, error)
 }
@@ -100,22 +100,86 @@ func executeGitCommand(dir string, cmd []string) ([]byte, error) {
 	return output, nil
 }
 
+func isTag(repoDir string, branchName string) bool {
+	cmd := []string{"tag", "--list"}
+	output, _ := executeGitCommand(repoDir, cmd)
+	return strings.Contains(string(output), branchName)
+}
+
+func branchExists(repoDir string, branchName string) bool {
+	cmd := []string{"show-ref", "--verify", "--quiet", "refs/heads/" + branchName}
+	_, err := executeGitCommand(repoDir, cmd)
+	return err == nil
+}
+
+// checkout does checkout a branch, or create a new branch from a tag name
+// if the commit supplied is a tag. sha1 based commits have no special
+// handling and result in dettached from HEAD state.
 func checkout(repoDir string, commit string) error {
-	cmd := []string{
-		"checkout",
-		commit,
+	var cmd []string
+	if isTag(repoDir, commit) && !branchExists(repoDir, commit) {
+		util.StatusMessage(util.VERBOSITY_VERBOSE, "Will create new branch %s"+
+			" from tag %s\n", commit, "tags/"+commit)
+		cmd = []string{
+			"checkout",
+			"tags/" + commit,
+			"-b",
+			commit,
+		}
+	} else {
+		util.StatusMessage(util.VERBOSITY_VERBOSE, "Will checkout branch %s\n",
+			commit)
+		cmd = []string{
+			"checkout",
+			commit,
+		}
 	}
 	_, err := executeGitCommand(repoDir, cmd)
 	return err
 }
 
-func pull(repoDir string) error {
-	_, err := executeGitCommand(repoDir, []string{"pull"})
+// mergeBranches applies upstream changes to the local copy and must be
+// preceeded by a "fetch" to achieve any meaningful result.
+func mergeBranches(repoDir string) {
+	branches := []string{"master", "develop"}
+	for _, branch := range branches {
+		err := checkout(repoDir, branch)
+		if err != nil {
+			continue
+		}
+		_, err = executeGitCommand(repoDir, []string{"merge", "origin/" + branch})
+		if err != nil {
+			util.StatusMessage(util.VERBOSITY_VERBOSE, "Merging changes from origin/%s: %s\n",
+				branch, err)
+		} else {
+			util.StatusMessage(util.VERBOSITY_VERBOSE, "Merging changes from origin/%s\n",
+				branch)
+		}
+		// XXX: ignore error, probably resulting from a branch not available at
+		//      origin anymore.
+	}
+}
+
+func fetch(repoDir string) error {
+	util.StatusMessage(util.VERBOSITY_VERBOSE, "Fetching new remote branches/tags\n")
+	_, err := executeGitCommand(repoDir, []string{"fetch", "--tags"})
 	return err
 }
 
-func stash(repoDir string) error {
-	_, err := executeGitCommand(repoDir, []string{"stash"})
+// stash saves current changes locally and returns if a new stash was
+// created (if there where no changes, there's no need to stash)
+func stash(repoDir string) (bool, error) {
+	util.StatusMessage(util.VERBOSITY_VERBOSE, "Stashing local changes\n")
+	output, err := executeGitCommand(repoDir, []string{"stash"})
+	if err != nil {
+		return false, err
+	}
+	return strings.Contains(string(output), "Saved"), nil
+}
+
+func stashPop(repoDir string) error {
+	util.StatusMessage(util.VERBOSITY_VERBOSE, "Un-stashing local changes\n")
+	_, err := executeGitCommand(repoDir, []string{"stash", "pop"})
 	return err
 }
 
@@ -198,27 +262,45 @@ func (gd *GithubDownloader) CurrentBranch(path string) (string, error) {
 	return strings.Trim(string(branch), "\r\n"), err
 }
 
-func (gd *GithubDownloader) UpdateRepo(path string) error {
-	return pull(path)
-}
-
-func (gd *GithubDownloader) CleanupRepo(path string, branchName string) error {
-	err := stash(path)
+func (gd *GithubDownloader) UpdateRepo(path string, branchName string) error {
+	err := fetch(path)
 	if err != nil {
 		return err
 	}
 
-	err = clean(path)
+	stashed, err := stash(path)
 	if err != nil {
 		return err
 	}
 
+	mergeBranches(path)
+
 	err = checkout(path, branchName)
 	if err != nil {
 		return err
 	}
 
-	return pull(path)
+	if stashed {
+		return stashPop(path)
+	}
+
+	return nil
+}
+
+func (gd *GithubDownloader) CleanupRepo(path string, branchName string) error {
+	_, err := stash(path)
+	if err != nil {
+		return err
+	}
+
+	err = clean(path)
+	if err != nil {
+		return err
+	}
+
+	// TODO: needs handling of non-tracked files
+
+	return gd.UpdateRepo(path, branchName)
 }
 
 func (gd *GithubDownloader) LocalDiff(path string) ([]byte, error) {
@@ -303,7 +385,7 @@ func (ld *LocalDownloader) CurrentBranch(path string) (string, error) {
 }
 
 // NOTE: intentionally always error...
-func (ld *LocalDownloader) UpdateRepo(path string) error {
+func (ld *LocalDownloader) UpdateRepo(path string, branchName string) error {
 	return util.NewNewtError(fmt.Sprintf("Can't pull from a local repo\n"))
 }
 

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/0418ceab/newt/repo/repo.go
----------------------------------------------------------------------
diff --git a/newt/repo/repo.go b/newt/repo/repo.go
index a4a916f..139c499 100644
--- a/newt/repo/repo.go
+++ b/newt/repo/repo.go
@@ -378,11 +378,11 @@ func (r *Repo) checkExists() bool {
 	return util.NodeExist(r.Path())
 }
 
-func (r *Repo) updateRepo() error {
+func (r *Repo) updateRepo(branchName string) error {
 	dl := r.downloader
-	err := dl.UpdateRepo(r.Path())
+	err := dl.UpdateRepo(r.Path(), branchName)
 	if err != nil {
-		return util.NewNewtError(fmt.Sprintf("\tError updating\n"))
+		return util.NewNewtError(fmt.Sprintf("Error updating\n"))
 	}
 	return nil
 }
@@ -391,7 +391,7 @@ func (r *Repo) cleanupRepo(branchName string) error {
 	dl := r.downloader
 	err := dl.CleanupRepo(r.Path(), branchName)
 	if err != nil {
-		return util.NewNewtError(fmt.Sprintf("\tError cleaning and updating\n"))
+		return util.NewNewtError(fmt.Sprintf("Error cleaning and updating\n"))
 	}
 	return nil
 }
@@ -431,33 +431,15 @@ func (r *Repo) currentBranch() (string, error) {
 		return "", util.NewNewtError(fmt.Sprintf("Error finding current branch for \"%s\" : %s",
 			r.Name(), err.Error()))
 	}
-	return branch, nil
-}
-
-func (r *Repo) checkForceInstall(force bool) (error, bool) {
-	// Copy the git repo into /repos/, error'ing out if the repo already exists
-	if util.NodeExist(r.Path()) {
-		if force {
-			if err := os.RemoveAll(r.Path()); err != nil {
-				return util.NewNewtError(err.Error()), true
-			}
-		}
-		return nil, true
-	}
-	return nil, false
+	return filepath.Base(branch), nil
 }
 
 func (r *Repo) Install(force bool) (*Version, error) {
-	if err, exists := r.checkForceInstall(force); err != nil || exists {
-		if err == nil {
-			if !force {
-				return nil, util.NewNewtError(fmt.Sprintf(
-					"Repository %s already exists, provide the -f option "+
-						"to overwrite", r.Name()))
-			}
-		} else {
-			return nil, err
-		}
+	exists := util.NodeExist(r.Path())
+	if exists && !force {
+		return nil, util.NewNewtError(fmt.Sprintf(
+			"Repository %s already exists, provide the -f option "+
+				"to overwrite", r.Name()))
 	}
 
 	branchName, vers, found := r.rdesc.Match(r)
@@ -466,6 +448,20 @@ func (r *Repo) Install(force bool) (*Version, error) {
 			r.rdesc.String()))
 	}
 
+	// if the repo is already cloned, try to cleanup and checkout the requested branch
+	if exists {
+		err := r.cleanupRepo(branchName)
+		if err == nil {
+			return vers, nil
+		}
+
+		// cleanup failed, so remove current copy and let download clone again...
+		if err := os.RemoveAll(r.Path()); err != nil {
+			return nil, util.NewNewtError(err.Error())
+		}
+	}
+
+	// repo was not already cloned or cleanup failed...
 	if err := r.downloadRepo(branchName); err != nil {
 		return nil, err
 	}
@@ -496,10 +492,13 @@ func (r *Repo) Sync(vers *Version, force bool) (bool, bool, error) {
 		// the user must know what he's doing...
 		// but, if -f is passed let's just save the work and re-clone
 		currBranch, err = r.currentBranch()
+
+		// currBranch == HEAD means we're dettached from HEAD, so
+		// ignore and move to "new" tag
 		if err != nil {
 			return exists, false, err
-		} else if currBranch != branchName {
-			msg := "Unexpected local branch for %s: \"%s\" != \"%s\""
+		} else if currBranch != "HEAD" && currBranch != branchName {
+			msg := "Unexpected local branch for %s: \"%s\" != \"%s\"\n"
 			if force {
 				util.StatusMessage(util.VERBOSITY_VERBOSE,
 					msg, r.rdesc.name, currBranch, branchName)
@@ -511,14 +510,14 @@ func (r *Repo) Sync(vers *Version, force bool) (bool, bool, error) {
 		}
 
 		// Don't try updating if on an invalid branch...
-		if currBranch == branchName {
-			util.StatusMessage(util.VERBOSITY_VERBOSE, "\tTrying to update repository... ")
-			err = r.updateRepo()
+		if currBranch == "HEAD" || currBranch == branchName {
+			util.StatusMessage(util.VERBOSITY_VERBOSE, "Updating repository...\n")
+			err = r.updateRepo(branchName)
 			if err == nil {
-				util.StatusMessage(util.VERBOSITY_VERBOSE, " success!\n")
+				util.StatusMessage(util.VERBOSITY_VERBOSE, "Update successful!\n")
 				return exists, true, err
 			} else {
-				util.StatusMessage(util.VERBOSITY_VERBOSE, " failed!\n")
+				util.StatusMessage(util.VERBOSITY_VERBOSE, "Update failed!\n")
 				if !force {
 					return exists, false, err
 				}
@@ -566,6 +565,7 @@ func (r *Repo) UpdateDesc() ([]*Repo, bool, error) {
 
 	_, repos, err := r.ReadDesc()
 	if err != nil {
+		fmt.Printf("ReadDesc: %v\n", err)
 		return nil, false, err
 	}
 
@@ -578,8 +578,8 @@ func (r *Repo) UpdateDesc() ([]*Repo, bool, error) {
 func (r *Repo) DownloadDesc() error {
 	dl := r.downloader
 
-	util.StatusMessage(util.VERBOSITY_VERBOSE, "\tDownloading "+
-		"repository description... ")
+	util.StatusMessage(util.VERBOSITY_VERBOSE, "Downloading "+
+		"repository description\n")
 
 	// Configuration path
 	cpath := r.repoFilePath()
@@ -592,7 +592,7 @@ func (r *Repo) DownloadDesc() error {
 	dl.SetBranch("master")
 	if err := dl.FetchFile("repository.yml",
 		cpath+"/"+"repository.yml"); err != nil {
-		util.StatusMessage(util.VERBOSITY_VERBOSE, " failed\n")
+		util.StatusMessage(util.VERBOSITY_VERBOSE, "Download failed\n")
 		return err
 	}
 
@@ -604,7 +604,7 @@ func (r *Repo) DownloadDesc() error {
 		}
 	}
 
-	util.StatusMessage(util.VERBOSITY_VERBOSE, " success!\n")
+	util.StatusMessage(util.VERBOSITY_VERBOSE, "Download successful!\n")
 
 	return nil
 }