You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mynewt.apache.org by cc...@apache.org on 2019/07/30 18:41:31 UTC
[mynewt-newt] branch master updated: Use "detached head" instead of
local branches
This is an automated email from the ASF dual-hosted git repository.
ccollins pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mynewt-newt.git
The following commit(s) were added to refs/heads/master by this push:
new 6a51e35 Use "detached head" instead of local branches
6a51e35 is described below
commit 6a51e35565323ebe8feb8d1aa6e00960b6ce662e
Author: Christopher Collins <cc...@apache.org>
AuthorDate: Thu Jul 18 13:23:00 2019 -0700
Use "detached head" instead of local branches
SUMMARY:
This commit changes how newt interacts with git. Newt used to use a mix
of "detached head" and local branches. With this commit, newt uses
"detached head" exclusively.
OLD BEHAVIOR:
Newt used a combination of "detached head" and local branches.
When upgrading to a version mapped to a branch, newt would
check out a local branch with the appropriate name. For example,
when upgrading to 0.0.0, newt would check out a local "master" branch.
When upgrading to a version mapped to a tag or a commit hash, newt would
put the repo in a "detached head" state.
This inconsistency comes from the `git checkout` command. Git puts a
repo in a different state depending on the type of commit being checked
out.
NEW BEHAVIOR:
Newt uses "detached head" exclusively. When upgrading to a version
mapped to a branch or tag, newt looks up the corresponding commit hash
and checks it out.
In addition, this commit adds a clarification in semantics:
Only the following commands make modifications to git repos:
* new
* install
* upgrade
* info -r
All other commands leave the git repos untouched. E.g., they do not
run `git fetch`, `git checkout`, or `git remote set-url`.
This change simplifies working with custom branches.
IMPROVEMENTS:
This change improves newt in several ways:
1. Lower chance of git conflicts. When newt used local branches, there
was always a chance of pulling incompatible commits into an existing
branch. This was especially likely when using `newt sync` and changing
a repo's remote URL.
2. No need for the `newt sync` command. The sync command was only
necessary due to newt's use of local branches. Now, `newt upgrade`
alone does the right thing: it fetches from `origin` and checks out the
correct commit. For branch-backed versions, newt checks out the latest
commit in the branch.
The `newt sync` command has been deprecated. Now it prints a warning
message and performs a `newt upgrade`.
3. Proper support for branches and tags in `project.yml`. Newt has
always allowed the `<hash>-commit` notation for a repo version string,
but it did not correctly handle `<branch>-commit` or `<tag>-commit`.
Now these version strings work correctly.
---
newt/cli/project_cmds.go | 8 +-
newt/downloader/downloader.go | 516 +++++++++++++++++++++---------------------
newt/install/install.go | 106 +++------
newt/project/project.go | 39 ++--
newt/repo/repo.go | 90 +++-----
newt/repo/version.go | 22 +-
6 files changed, 344 insertions(+), 437 deletions(-)
diff --git a/newt/cli/project_cmds.go b/newt/cli/project_cmds.go
index 4204524..9344e0f 100644
--- a/newt/cli/project_cmds.go
+++ b/newt/cli/project_cmds.go
@@ -183,8 +183,10 @@ func syncRunCmd(cmd *cobra.Command, args []string) {
proj := TryGetProject()
pred := makeRepoPredicate(args)
- if err := proj.SyncIf(
- newtutil.NewtForce, newtutil.NewtAsk, pred); err != nil {
+ util.OneTimeWarning("\"sync\" is deprecated. Use \"upgrade\" instead.")
+
+ if err := proj.InstallIf(
+ true, newtutil.NewtForce, newtutil.NewtAsk, pred); err != nil {
NewtUsage(nil, err)
}
@@ -239,7 +241,7 @@ func AddProjectCommands(cmd *cobra.Command) {
syncHelpEx += " Syncs the apache-mynewt-core repository."
syncCmd := &cobra.Command{
Use: "sync [repo-1] [repo-2] [...]",
- Short: "Synchronize project dependencies",
+ Short: "Synchronize project dependencies (deprecated)",
Long: syncHelpText,
Example: syncHelpEx,
Run: syncRunCmd,
diff --git a/newt/downloader/downloader.go b/newt/downloader/downloader.go
index 99a21d8..1e58ea4 100644
--- a/newt/downloader/downloader.go
+++ b/newt/downloader/downloader.go
@@ -56,8 +56,12 @@ type Downloader interface {
// (i.e., 1 hash, n tags, and n branches).
CommitsFor(path string, commit string) ([]string, error)
- // Fetches all remotes and merges the specified branch into the local repo.
- Pull(path string, branchName string) error
+ // Fetches all remotes.
+ Fetch(path string) error
+
+ // Checks out the specified commit (hash, tag, or branch). Always puts the
+ // repo in a "detached head" state.
+ Checkout(path string, commit string) error
// Indicates whether the repo is in a clean or dirty state.
DirtyState(path string) (string, error)
@@ -69,16 +73,24 @@ type Downloader interface {
// user's `project.yml` file and / or the repo dependency lists.
FixupOrigin(path string) error
- // Retrieves the name of the currently checked out branch, or "" if no
- // branch is checked out.
+ // Retrieves the name of the currently checked out local branch, or "" if
+ // the repo is in a "detached head" state.
CurrentBranch(path string) (string, error)
+}
- // Retrieves the name of the remote branch being tracked by the specified
- // local branch, or "" if there is no tracked remote branch.
- UpstreamFor(repoDir string, branch string) (string, error)
+type Commit struct {
+ hash string
+ name string
+ typ DownloaderCommitType
}
type GenericDownloader struct {
+ // [name-of-branch-or-tag]commit
+ commits map[string]Commit
+
+ // Hash of checked-out commit.
+ head string
+
// Whether 'origin' has been fetched during this run.
fetched bool
}
@@ -188,80 +200,12 @@ func updateSubmodules(path string) error {
return 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 {
- var cmd []string
- ct, err := commitType(repoDir, commit)
- if err != nil {
- return err
- }
-
- full, err := remoteCommitName(repoDir, commit)
- if err != nil {
- return err
- }
-
- if ct == COMMIT_TYPE_TAG {
- util.StatusMessage(util.VERBOSITY_VERBOSE, "Will create new branch %s"+
- " from %s\n", commit, full)
- cmd = []string{
- "checkout",
- full,
- "-b",
- commit,
- }
- } else {
- util.StatusMessage(util.VERBOSITY_VERBOSE, "Will checkout %s\n", full)
- cmd = []string{
- "checkout",
- commit,
- }
- }
- if _, err := executeGitCommand(repoDir, cmd, true); err != nil {
- return err
- }
-
- // Always initialize and update submodules on checkout. This prevents the
- // repo from being in a modified "(new commits)" state immediately after
- // switching commits. If the submodules have already been updated, this
- // does not generate any network activity.
- if err := initSubmodules(repoDir); err != nil {
- return err
- }
- if err := updateSubmodules(repoDir); err != nil {
- return err
- }
-
- return nil
-}
-
-// rebase applies upstream changes to the local copy and must be
-// preceeded by a "fetch" to achieve any meaningful result.
-func rebase(repoDir string, commit string) error {
- if err := checkout(repoDir, commit); err != nil {
- return err
- }
-
- // We want to rebase the remote version of this branch.
- full, err := remoteCommitName(repoDir, commit)
- if err != nil {
- return err
- }
-
- cmd := []string{
- "rebase",
- full}
- if _, err := executeGitCommand(repoDir, cmd, true); err != nil {
- util.StatusMessage(util.VERBOSITY_VERBOSE,
- "Merging changes from %s: %s\n", full, err)
- return err
- }
-
- util.StatusMessage(util.VERBOSITY_VERBOSE,
- "Merging changes from %s\n", full)
- return nil
+// fixupCommitString strips "origin/" from the front of a commit, if it is
+// present. Newt only works with remote branches, and only with the "origin"
+// remote. The user is not required to prefix his branch specifiers with
+// "origin/", but is allowed to.
+func fixupCommitString(s string) string {
+ return strings.TrimPrefix(s, "origin/")
}
func mergeBase(repoDir string, commit string) (string, error) {
@@ -278,39 +222,6 @@ func mergeBase(repoDir string, commit string) (string, error) {
return strings.TrimSpace(string(o)), nil
}
-func branchExists(repoDir string, branchName string) bool {
- cmd := []string{
- "show-ref",
- "--verify",
- "--quiet",
- "refs/heads/" + branchName,
- }
- _, err := executeGitCommand(repoDir, cmd, true)
- return err == nil
-}
-
-func commitType(repoDir string, commit string) (DownloaderCommitType, error) {
- if commit == "HEAD" {
- return COMMIT_TYPE_HASH, nil
- }
-
- if _, err := mergeBase(repoDir, commit); err == nil {
- // Distinguish local branch from hash.
- if branchExists(repoDir, commit) {
- return COMMIT_TYPE_BRANCH, nil
- } else {
- return COMMIT_TYPE_HASH, nil
- }
- }
-
- if _, err := mergeBase(repoDir, "tags/"+commit); err == nil {
- return COMMIT_TYPE_TAG, nil
- }
-
- return DownloaderCommitType(-1), util.FmtNewtError(
- "Cannot determine commit type of \"%s\"", commit)
-}
-
func upstreamFor(path string, commit string) (string, error) {
cmd := []string{
"rev-parse",
@@ -331,64 +242,6 @@ func upstreamFor(path string, commit string) (string, error) {
return strings.TrimSpace(string(up)), nil
}
-func remoteCommitName(path string, commit string) (string, error) {
- ct, err := commitType(path, commit)
- if err != nil {
- return "", err
- }
-
- switch ct {
- case COMMIT_TYPE_BRANCH:
- rmt, err := upstreamFor(path, commit)
- if err != nil {
- return "", err
- }
- if rmt == "" {
- return "",
- util.FmtNewtError("No remote upstream for branch \"%s\"",
- commit)
- }
- return rmt, nil
- case COMMIT_TYPE_TAG:
- return "tags/" + commit, nil
- case COMMIT_TYPE_HASH:
- return commit, nil
- default:
- return "", util.FmtNewtError("unknown commit type: %d", int(ct))
- }
-}
-
-func showFile(
- path string, branch string, filename string, dstDir string) error {
-
- if err := os.MkdirAll(dstDir, os.ModePerm); err != nil {
- return util.ChildNewtError(err)
- }
-
- full, err := remoteCommitName(path, branch)
- if err != nil {
- return err
- }
-
- cmd := []string{
- "show",
- fmt.Sprintf("%s:%s", full, filename),
- }
-
- dstPath := fmt.Sprintf("%s/%s", dstDir, filename)
- log.Debugf("Fetching file %s to %s", filename, dstPath)
- data, err := executeGitCommand(path, cmd, true)
- if err != nil {
- return err
- }
-
- if err := ioutil.WriteFile(dstPath, data, os.ModePerm); err != nil {
- return util.ChildNewtError(err)
- }
-
- return nil
-}
-
func getRemoteUrl(path string, remote string) (string, error) {
cmd := []string{
"remote",
@@ -426,80 +279,249 @@ func warnWrongOriginUrl(path string, curUrl string, goodUrl string) {
curUrl, goodUrl)
}
-func (gd *GenericDownloader) CommitType(
- path string, commit string) (DownloaderCommitType, error) {
+// getCommits gathers all tags and remote branches. It returns a mapping of
+// [name]commit.
+func getCommits(path string) (map[string]Commit, error) {
+ cmd := []string{"show-ref", "--dereference"}
+ o, err := executeGitCommand(path, cmd, true)
+ if err != nil {
+ return nil, err
+ }
+
+ // Example output:
+ // b7a5474d569d5b67152d1773627ddda010c080a3 refs/remotes/origin/1_7_0_dev
+ // da13fb50c3b5824c47a44b62c3c9f693b922ce9c refs/tags/mynewt_1_7_0_tag
+ // b7a5474d569d5b67152d1773627ddda010c080a3 refs/tags/mynewt_1_7_0_tag^{}
+
+ m := map[string]Commit{}
- return commitType(path, commit)
+ lines := strings.Split(strings.TrimSpace(string(o)), "\n")
+ for _, line := range lines {
+ f := strings.Fields(line)
+ if len(f) != 2 {
+ return nil, util.FmtNewtError(
+ "git show-ref produced unexpected line: \"%s\"", line)
+ }
+
+ hash := f[0]
+ ref := strings.TrimSuffix(f[1], "^{}")
+
+ c := Commit{
+ hash: hash,
+ }
+ if n := strings.TrimPrefix(ref, "refs/remotes/origin/"); n != ref {
+ c.typ = COMMIT_TYPE_BRANCH
+ c.name = n
+ } else if n := strings.TrimPrefix(ref, "refs/tags/"); n != ref {
+ c.typ = COMMIT_TYPE_TAG
+ c.name = n
+ }
+
+ if c.name != "" {
+ m[c.name] = c
+ }
+ }
+
+ return m, nil
}
-func (gd *GenericDownloader) HashFor(path string, commit string) (string, error) {
- full, err := remoteCommitName(path, commit)
+// init populates a generic downloader with branch and tag information.
+func (gd *GenericDownloader) init(path string) error {
+ cmap, err := getCommits(path)
if err != nil {
- return "", err
+ return err
}
- cmd := []string{"rev-parse", full}
+ gd.commits = cmap
+
+ cmd := []string{"rev-parse", "HEAD"}
o, err := executeGitCommand(path, cmd, true)
if err != nil {
- return "", err
+ return err
}
+ gd.head = strings.TrimSpace(string(o))
- return strings.TrimSpace(string(o)), nil
+ return nil
}
-func (gd *GenericDownloader) CommitsFor(
- path string, commit string) ([]string, error) {
+// ensureInited calls init on the provided downloader if it has not already
+// been initialized.
+func (gd *GenericDownloader) ensureInited(path string) error {
+ if gd.commits != nil {
+ // Already initialized.
+ return nil
+ }
- // Hash.
- hash, err := gd.HashFor(path, commit)
+ return gd.init(path)
+}
+
+func (gd *GenericDownloader) Checkout(repoDir string, commit string) error {
+ // Get the hash corresponding to the commit in case the caller specified a
+ // branch or tag. We always want to check out a hash and end up in a
+ // "detached head" state.
+ hash, err := gd.HashFor(repoDir, commit)
if err != nil {
- return nil, err
+ return err
}
- // Branches and tags.
+ util.StatusMessage(util.VERBOSITY_VERBOSE, "Will checkout %s\n", hash)
cmd := []string{
- "for-each-ref",
- "--format=%(refname:short)",
- "--points-at",
+ "checkout",
hash,
}
- o, err := executeGitCommand(path, cmd, true)
+
+ if _, err := executeGitCommand(repoDir, cmd, true); err != nil {
+ return err
+ }
+
+ // Always initialize and update submodules on checkout. This prevents the
+ // repo from being in a modified "(new commits)" state immediately after
+ // switching commits. If the submodules have already been updated, this
+ // does not generate any network activity.
+ if err := initSubmodules(repoDir); err != nil {
+ return err
+ }
+ if err := updateSubmodules(repoDir); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (gd *GenericDownloader) showFile(
+ path string, commit string, filename string, dstDir string) error {
+
+ if err := os.MkdirAll(dstDir, os.ModePerm); err != nil {
+ return util.ChildNewtError(err)
+ }
+
+ hash, err := gd.HashFor(path, commit)
if err != nil {
+ return err
+ }
+
+ dstPath := fmt.Sprintf("%s/%s", dstDir, filename)
+ log.Debugf("Fetching file %s to %s", filename, dstPath)
+
+ cmd := []string{"show", fmt.Sprintf("%s:%s", hash, filename)}
+ data, err := executeGitCommand(path, cmd, true)
+ if err != nil {
+ return err
+ }
+
+ if err := ioutil.WriteFile(dstPath, data, os.ModePerm); err != nil {
+ return util.ChildNewtError(err)
+ }
+
+ return nil
+}
+
+func (gd *GenericDownloader) findCommit(s string) *Commit {
+ c, ok := gd.commits[fixupCommitString(s)]
+ if !ok {
+ return nil
+ } else {
+ return &c
+ }
+}
+
+func (gd *GenericDownloader) CommitType(
+ path string, commit string) (DownloaderCommitType, error) {
+
+ if err := gd.ensureInited(path); err != nil {
+ return -1, err
+ }
+
+ // HEAD is always a commit hash (detached).
+ if commit == "HEAD" {
+ return COMMIT_TYPE_HASH, nil
+ }
+
+ // Check if user provided a branch or tag name.
+ if c := gd.findCommit(commit); c != nil {
+ return c.typ, nil
+ }
+
+ // Check if user provided a commit hash.
+ if _, err := mergeBase(path, commit); err == nil {
+ return COMMIT_TYPE_HASH, nil
+ }
+
+ return -1, util.FmtNewtError(
+ "cannot determine commit type of \"%s\"", commit)
+}
+
+func (gd *GenericDownloader) HashFor(path string,
+ commit string) (string, error) {
+
+ if err := gd.ensureInited(path); err != nil {
+ return "", err
+ }
+
+ if commit == "HEAD" {
+ return gd.head, nil
+ }
+
+ if c := gd.findCommit(commit); c != nil {
+ return c.hash, nil
+ }
+
+ return commit, nil
+}
+
+func (gd *GenericDownloader) CommitsFor(
+ path string, commit string) ([]string, error) {
+
+ if err := gd.ensureInited(path); err != nil {
return nil, err
}
- lines := []string{hash}
- text := strings.TrimSpace(string(o))
- if text != "" {
- lines = append(lines, strings.Split(text, "\n")...)
+ commit = fixupCommitString(commit)
+
+ var commits []string
+
+ // Always insert the specified string into the set.
+ commits = append(commits, commit)
+
+ // Add all commits that are equivalent to the specified string.
+ for _, c := range gd.commits {
+ if commit == c.hash {
+ // User specified a hash; add the corresponding branch or tag name.
+ commits = append(commits, c.name)
+ } else if commit == c.name {
+ // User specified a branch or tag; add the corresponding hash.
+ commits = append(commits, c.hash)
+ }
}
- sort.Strings(lines)
- return lines, nil
+ sort.Strings(commits)
+ return commits, nil
}
func (gd *GenericDownloader) CurrentBranch(path string) (string, error) {
- cmd := []string{
- "rev-parse",
- "--abbrev-ref",
- "HEAD",
- }
+ // Check if there is a git ref (branch) for the current commit. If there
+ // is none, git exits with a status of 1. We need to distinguish this case
+ // from an actual error.
+ cmd := []string{"symbolic-ref", "-q", "HEAD"}
o, err := executeGitCommand(path, cmd, true)
if err != nil {
- return "", err
+ ne := err.(*util.NewtError)
+ ee, ok := ne.Parent.(*exec.ExitError)
+ if ok && ee.ExitCode() == 1 {
+ // No branch.
+ return "", nil
+ } else {
+ return "", err
+ }
}
s := strings.TrimSpace(string(o))
- if s == "HEAD" {
- return "", nil
- } else {
- return s, nil
+ branch := strings.TrimPrefix(s, "refs/heads/")
+ if branch == s {
+ return "", util.FmtNewtError(
+ "%s produced unexpected output: %s", strings.Join(cmd, " "), s)
}
-}
-func (gd *GenericDownloader) UpstreamFor(repoDir string,
- branch string) (string, error) {
-
- return upstreamFor(repoDir, branch)
+ return branch, nil
}
// Fetches the downloader's origin remote if it hasn't been fetched yet during
@@ -586,12 +608,13 @@ func (gd *GenericDownloader) DirtyState(path string) (string, error) {
return "", nil
}
-func (gd *GithubDownloader) fetch(repoDir string) error {
+func (gd *GithubDownloader) Fetch(repoDir string) error {
return gd.cachedFetch(func() error {
util.StatusMessage(util.VERBOSITY_VERBOSE, "Fetching repo %s\n",
gd.Repo)
- _, err := gd.authenticatedCommand(repoDir, []string{"fetch", "--tags"})
+ cmd := []string{"fetch", "--tags"}
+ _, err := gd.authenticatedCommand(repoDir, cmd)
return err
})
}
@@ -620,28 +643,11 @@ func (gd *GithubDownloader) authenticatedCommand(path string,
func (gd *GithubDownloader) FetchFile(
commit string, path string, filename string, dstDir string) error {
- if err := gd.fetch(path); err != nil {
- return err
- }
-
- if err := showFile(path, commit, filename, dstDir); err != nil {
+ if err := gd.Fetch(path); err != nil {
return err
}
- return nil
-}
-
-func (gd *GithubDownloader) Pull(path string, branchName string) error {
- err := gd.fetch(path)
- if err != nil {
- return err
- }
-
- // Ignore error, probably resulting from a branch not available at origin
- // anymore.
- rebase(path, branchName)
-
- if err := checkout(path, branchName); err != nil {
+ if err := gd.showFile(path, commit, filename, dstDir); err != nil {
return err
}
@@ -731,11 +737,9 @@ func (gd *GithubDownloader) Clone(commit string, dstPath string) error {
if err != nil {
return err
}
-
defer gd.clearRemoteAuth(dstPath)
- // Checkout the specified commit.
- if err := checkout(dstPath, commit); err != nil {
+ if err := gd.Checkout(dstPath, commit); err != nil {
return err
}
@@ -762,10 +766,8 @@ func NewGithubDownloader() *GithubDownloader {
return &GithubDownloader{}
}
-func (gd *GitDownloader) fetch(repoDir string) error {
+func (gd *GitDownloader) Fetch(repoDir string) error {
return gd.cachedFetch(func() error {
- util.StatusMessage(util.VERBOSITY_VERBOSE, "Fetching repo %s\n",
- gd.Url)
_, err := executeGitCommand(repoDir, []string{"fetch", "--tags"}, true)
return err
})
@@ -774,28 +776,11 @@ func (gd *GitDownloader) fetch(repoDir string) error {
func (gd *GitDownloader) FetchFile(
commit string, path string, filename string, dstDir string) error {
- if err := gd.fetch(path); err != nil {
- return err
- }
-
- if err := showFile(path, commit, filename, dstDir); err != nil {
- return err
- }
-
- return nil
-}
-
-func (gd *GitDownloader) Pull(path string, branchName string) error {
- err := gd.fetch(path)
- if err != nil {
+ if err := gd.Fetch(path); err != nil {
return err
}
- // Ignore error, probably resulting from a branch not available at origin
- // anymore.
- rebase(path, branchName)
-
- if err := checkout(path, branchName); err != nil {
+ if err := gd.showFile(path, commit, filename, dstDir); err != nil {
return err
}
@@ -833,8 +818,7 @@ func (gd *GitDownloader) Clone(commit string, dstPath string) error {
return err
}
- // Checkout the specified commit.
- if err := checkout(dstPath, commit); err != nil {
+ if err := gd.Checkout(dstPath, commit); err != nil {
return err
}
@@ -873,9 +857,14 @@ func (ld *LocalDownloader) FetchFile(
return nil
}
-func (ld *LocalDownloader) Pull(path string, branchName string) error {
+func (ld *LocalDownloader) Fetch(path string) error {
os.RemoveAll(path)
- return ld.Clone(branchName, path)
+ return ld.Clone("master", path)
+}
+
+func (ld *LocalDownloader) Checkout(path string, commit string) error {
+ _, err := executeGitCommand(path, []string{"checkout", commit}, true)
+ return err
}
func (ld *LocalDownloader) Clone(commit string, dstPath string) error {
@@ -886,8 +875,7 @@ func (ld *LocalDownloader) Clone(commit string, dstPath string) error {
return err
}
- // Checkout the specified commit.
- if err := checkout(dstPath, commit); err != nil {
+ if err := ld.Checkout(dstPath, commit); err != nil {
return err
}
diff --git a/newt/install/install.go b/newt/install/install.go
index 0c08032..5244999 100644
--- a/newt/install/install.go
+++ b/newt/install/install.go
@@ -133,7 +133,6 @@ type installOp int
const (
INSTALL_OP_INSTALL installOp = iota
INSTALL_OP_UPGRADE
- INSTALL_OP_SYNC
)
// Determines the currently installed version of the specified repo. If the
@@ -408,16 +407,20 @@ func (inst *Installer) shouldUpgradeRepo(
"internal error: nonexistent repo has version: %s", repoName)
}
- if !r.VersionsEqual(*curVer, destVer) {
+ // If the repo is not in a "detached head" state, it needs to be fixed up.
+ detached, err := r.IsDetached()
+ if err != nil {
+ return false, err
+ }
+ if !detached {
return true, nil
}
- equiv, err := r.CommitsEquivalent(curVer.Commit, destVer.Commit)
- if err != nil {
- return false, err
+ if !r.VersionsEqual(*curVer, destVer) {
+ return true, nil
}
- return !equiv, nil
+ return false, nil
}
// Removes repos that shouldn't be upgraded from the specified list. A repo
@@ -460,7 +463,7 @@ func (inst *Installer) filterUpgradeList(
// Describes an imminent install or upgrade operation to the user. The
// displayed message applies to the specified repo.
func (inst *Installer) installMessageOneRepo(
- repoName string, op installOp, force bool, curVer *newtutil.RepoVersion,
+ r *repo.Repo, op installOp, force bool, curVer *newtutil.RepoVersion,
destVer newtutil.RepoVersion) (string, error) {
// If the repo isn't installed yet, this is an install, not an upgrade.
@@ -478,24 +481,22 @@ func (inst *Installer) installMessageOneRepo(
}
case INSTALL_OP_UPGRADE:
- verb = "upgrade"
-
- case INSTALL_OP_SYNC:
- verb = "sync"
+ if r.VersionsEqual(*curVer, destVer) {
+ verb = "fixup"
+ } else {
+ verb = "upgrade"
+ }
default:
return "", util.FmtNewtError(
"internal error: invalid install op: %v", op)
}
- msg := fmt.Sprintf(" %s %s ", verb, repoName)
- if op == INSTALL_OP_UPGRADE {
+ msg := fmt.Sprintf(" %s %s ", verb, r.Name())
+ if verb == "upgrade" {
msg += fmt.Sprintf("(%s --> %s)", curVer.String(), destVer.String())
- } else if op != INSTALL_OP_SYNC {
- msg += fmt.Sprintf("(%s)", destVer.String())
} else {
- // Sync operation. Don't print the project version. Instead, print
- // the actual branch name later during the sync.
+ msg += fmt.Sprintf("(%s)", destVer.String())
}
return msg, nil
@@ -526,7 +527,7 @@ func (inst *Installer) installPrompt(vm deprepo.VersionMap, op installOp,
destVer := vm[name]
msg, err := inst.installMessageOneRepo(
- name, op, force, curVer, destVer)
+ r, op, force, curVer, destVer)
if err != nil {
return false, err
}
@@ -880,59 +881,6 @@ func (inst *Installer) Upgrade(candidates []*repo.Repo, force bool,
return nil
}
-// Syncs the specified set of repos.
-func (inst *Installer) Sync(candidates []*repo.Repo,
- force bool, ask bool) error {
-
- if err := verifyRepoDirtyState(candidates, force); err != nil {
- return err
- }
-
- vm, err := inst.calcVersionMap(candidates)
- if err != nil {
- return err
- }
-
- // Notify the user of what install operations are about to happen, and
- // prompt if the `-a` (ask) option was specified.
- proceed, err := inst.installPrompt(vm, INSTALL_OP_SYNC, false, ask)
- if err != nil {
- return err
- }
- if !proceed {
- return nil
- }
-
- repos, err := inst.versionMapRepos(vm)
- if err != nil {
- return err
- }
-
- // Sync each repo in the list.
- var anyFails bool
- for _, r := range repos {
- ver := inst.installedVer(r.Name())
- if ver == nil {
- util.StatusMessage(util.VERBOSITY_DEFAULT,
- "No installed version of %s found, skipping\n",
- r.Name())
- } else {
- if _, err := r.Sync(*ver); err != nil {
- util.StatusMessage(util.VERBOSITY_QUIET,
- "Failed to sync repo \"%s\": %s\n",
- r.Name(), err.Error())
- anyFails = true
- }
- }
- }
-
- if anyFails {
- return util.FmtNewtError("Failed to sync")
- }
-
- return nil
-}
-
type repoInfo struct {
installedVer *newtutil.RepoVersion // nil if not installed.
commitHash string
@@ -953,6 +901,15 @@ func (inst *Installer) gatherInfo(r *repo.Repo,
return ri
}
+ if vm != nil {
+ // The caller requested a remote query. Download this repo's latest
+ // `repository.yml` file.
+ if err := r.DownloadDesc(); err != nil {
+ ri.errorText = strings.TrimSpace(err.Error())
+ return ri
+ }
+ }
+
commitHash, err := r.CurrentHash()
if err != nil {
ri.errorText = strings.TrimSpace(err.Error())
@@ -995,6 +952,13 @@ func (inst *Installer) Info(repos []*repo.Repo, remote bool) error {
var vmp *deprepo.VersionMap
if remote {
+ // Fetch the latest for all repos.
+ for _, r := range repos {
+ if err := r.DownloadDesc(); err != nil {
+ return err
+ }
+ }
+
vm, err := inst.calcVersionMap(repos)
if err != nil {
return err
diff --git a/newt/project/project.go b/newt/project/project.go
index 4707934..e67adb2 100644
--- a/newt/project/project.go
+++ b/newt/project/project.go
@@ -282,26 +282,6 @@ func (proj *Project) InstallIf(
}
}
-// Syncs (i.e., applies `git pull` to) repos matching the specified predicate.
-func (proj *Project) SyncIf(
- force bool, ask bool, predicate func(r *repo.Repo) bool) error {
-
- // Make sure we have an up to date copy of all `repository.yml` files.
- if err := proj.downloadRepositoryYmlFiles(); err != nil {
- return err
- }
-
- // Determine which repos the user wants to sync.
- repoList := proj.SelectRepos(predicate)
-
- inst, err := install.NewInstaller(proj.repos, proj.rootRepoReqs)
- if err != nil {
- return err
- }
-
- return inst.Sync(repoList, force, ask)
-}
-
func (proj *Project) InfoIf(predicate func(r *repo.Repo) bool,
remote bool) error {
@@ -426,7 +406,9 @@ func (proj *Project) loadRepoDeps(download bool) error {
return nil, err
}
}
- proj.repos[dep.Name] = depRepo
+ if err := proj.addRepo(depRepo); err != nil {
+ return nil, err
+ }
}
newRepos = append(newRepos, depRepo)
@@ -519,6 +501,17 @@ func (proj *Project) verifyNewtCompat() error {
return nil
}
+// addRepo Adds an entry to the project's repo map. It clones the repo if it
+// does not exist locally.
+func (proj *Project) addRepo(r *repo.Repo) error {
+ if err := r.EnsureExists(); err != nil {
+ return err
+ }
+
+ proj.repos[r.Name()] = r
+ return nil
+}
+
func (proj *Project) loadConfig() error {
yc, err := config.ReadFile(proj.BasePath + "/" + PROJECT_FILE_NAME)
if err != nil {
@@ -565,7 +558,9 @@ func (proj *Project) loadConfig() error {
repoName, fields["vers"], err.Error())
}
- proj.repos[repoName] = r
+ if err := proj.addRepo(r); err != nil {
+ return err
+ }
proj.rootRepoReqs[repoName] = verReqs
}
}
diff --git a/newt/repo/repo.go b/newt/repo/repo.go
index feae366..1c566b0 100644
--- a/newt/repo/repo.go
+++ b/newt/repo/repo.go
@@ -228,12 +228,16 @@ func (r *Repo) CheckExists() bool {
func (r *Repo) updateRepo(commit string) error {
// Clone the repo if it doesn't exist.
- if err := r.ensureExists(); err != nil {
+ if err := r.EnsureExists(); err != nil {
return err
}
// Fetch and checkout the specified commit.
- if err := r.downloader.Pull(r.Path(), commit); err != nil {
+ if err := r.downloader.Fetch(r.Path()); err != nil {
+ return util.FmtNewtError(
+ "Error updating \"%s\": %s", r.Name(), err.Error())
+ }
+ if err := r.downloader.Checkout(r.Path(), commit); err != nil {
return util.FmtNewtError(
"Error updating \"%s\": %s", r.Name(), err.Error())
}
@@ -276,71 +280,26 @@ func (r *Repo) Upgrade(ver newtutil.RepoVersion) error {
return nil
}
-// @return bool True if the sync succeeded.
-// @return error Fatal error.
-func (r *Repo) Sync(ver newtutil.RepoVersion) (bool, error) {
- // Sync is only allowed if a branch is checked out.
- branch, err := r.downloader.CurrentBranch(r.localPath)
- if err != nil {
- return false, err
- }
-
- if branch == "" {
- commits, err := r.CurrentCommits()
- if err != nil {
- return false, err
- }
-
- util.StatusMessage(util.VERBOSITY_DEFAULT,
- "Skipping \"%s\": not using a branch (current-commits=%v)\n",
- r.Name(), commits)
- return false, nil
- }
-
- // Determine the upstream associated with the current branch. This is the
- // upstream that will be pulled from.
- upstream, err := r.downloader.UpstreamFor(r.localPath, branch)
- if err != nil {
- return false, err
- }
- if upstream == "" {
- util.StatusMessage(util.VERBOSITY_QUIET,
- "Failed to sync repo \"%s\": no upstream being tracked "+
- "(branch=%s)\n",
- r.Name(), branch)
- return false, nil
- }
-
- util.StatusMessage(util.VERBOSITY_DEFAULT,
- "Syncing repository \"%s\" (%s)... ", r.Name(), upstream)
-
- // Pull from upstream.
- err = r.updateRepo(branch)
- if err == nil {
- util.StatusMessage(util.VERBOSITY_DEFAULT, "success\n")
- return true, nil
- } else {
- util.StatusMessage(util.VERBOSITY_QUIET, "failed: %s\n",
- strings.TrimSpace(err.Error()))
- return false, nil
- }
-}
-
// Fetches all remotes and downloads an up to date copy of `repository.yml`
// from master. The repo object is then populated with the contents of the
// downladed file. If this repo has already had its descriptor updated, this
// function is a no-op.
func (r *Repo) UpdateDesc() (bool, error) {
- var err error
-
if r.updated {
return false, nil
}
util.StatusMessage(util.VERBOSITY_VERBOSE, "[%s]:\n", r.Name())
+ // Make sure the repo's "origin" remote points to the correct URL. This is
+ // necessary in case the user changed his `project.yml` file to point to a
+ // different fork.
+ if err := r.downloader.FixupOrigin(r.localPath); err != nil {
+ return false, err
+ }
+
// Download `repository.yml`.
- if err = r.DownloadDesc(); err != nil {
+ if err := r.DownloadDesc(); err != nil {
return false, err
}
@@ -354,7 +313,7 @@ func (r *Repo) UpdateDesc() (bool, error) {
return true, nil
}
-func (r *Repo) ensureExists() error {
+func (r *Repo) EnsureExists() error {
// Clone the repo if it doesn't exist.
if !r.CheckExists() {
if err := r.downloadRepo("master"); err != nil {
@@ -362,13 +321,6 @@ func (r *Repo) ensureExists() error {
}
}
- // Make sure the repo's "origin" remote points to the correct URL. This is
- // necessary in case the user changed his `project.yml` file to point to a
- // different fork.
- if err := r.downloader.FixupOrigin(r.localPath); err != nil {
- return err
- }
-
return nil
}
@@ -376,7 +328,7 @@ func (r *Repo) downloadFile(commit string, srcPath string) (string, error) {
dl := r.downloader
// Clone the repo if it doesn't exist.
- if err := r.ensureExists(); err != nil {
+ if err := r.EnsureExists(); err != nil {
return "", err
}
@@ -435,6 +387,16 @@ func (r *Repo) DownloadDesc() error {
return nil
}
+// IsDetached indicates whether a repo is in a "detached head" state.
+func (r *Repo) IsDetached() (bool, error) {
+ branch, err := r.downloader.CurrentBranch(r.Path())
+ if err != nil {
+ return false, err
+ }
+
+ return branch == "", nil
+}
+
func parseRepoDepMap(depName string,
repoMapYml interface{}) (map[string]*RepoDependency, error) {
diff --git a/newt/repo/version.go b/newt/repo/version.go
index 580a9cf..70f437f 100644
--- a/newt/repo/version.go
+++ b/newt/repo/version.go
@@ -78,18 +78,15 @@ func normalizeCommit(commit string) string {
func (r *Repo) CurrentHash() (string, error) {
dl := r.downloader
if dl == nil {
- return "",
- util.FmtNewtError("No downloader for %s",
- r.Name())
+ return "", util.FmtNewtError("No downloader for %s", r.Name())
}
- commit, err := dl.HashFor(r.Path(), "HEAD")
+ hash, err := dl.HashFor(r.Path(), "HEAD")
if err != nil {
- return "",
- util.FmtNewtError("Error finding current hash for \"%s\": %s",
- r.Name(), err.Error())
+ return "", err
}
- return commit, nil
+
+ return hash, nil
}
// Retrieves all commit strings corresponding to the repo's current state.
@@ -293,10 +290,6 @@ func (r *Repo) NormalizeVerReqs(verReqs []newtutil.RepoVersionReq) (
func (r *Repo) VersionsEqual(v1 newtutil.RepoVersion,
v2 newtutil.RepoVersion) bool {
- if newtutil.CompareRepoVersions(v1, v2) == 0 {
- return true
- }
-
h1, err := r.HashFromVer(v1)
if err != nil {
return false
@@ -398,7 +391,7 @@ func (r *Repo) inferVersion(commit string, vyVer *newtutil.RepoVersion) (
"Version mismatch in %s:%s; repository.yml:%s, version.yml:%s",
r.Name(), commit, versString(ryVers), vyVer.String())
} else {
- // If the set of commits don't match a version from
+ // If the set of commits doesn't contain a version from
// `repository.yml`, record the commit hash in the version
// specifier. This will distinguish the returned version from its
// corresponding official release.
@@ -436,6 +429,9 @@ func (r *Repo) InstalledVersion() (*newtutil.RepoVersion, error) {
if err != nil {
return nil, err
}
+ if hash == "" {
+ return nil, nil
+ }
ver, err := r.inferVersion(hash, vyVer)
if err != nil {