diff options
Diffstat (limited to 'subcmds')
| -rw-r--r-- | subcmds/abandon.py | 27 | ||||
| -rw-r--r-- | subcmds/checkout.py | 24 | ||||
| -rw-r--r-- | subcmds/cherry_pick.py | 114 | ||||
| -rw-r--r-- | subcmds/diff.py | 15 | ||||
| -rw-r--r-- | subcmds/download.py | 21 | ||||
| -rw-r--r-- | subcmds/forall.py | 7 | ||||
| -rw-r--r-- | subcmds/help.py | 2 | ||||
| -rw-r--r-- | subcmds/init.py | 173 | ||||
| -rw-r--r-- | subcmds/list.py | 48 | ||||
| -rw-r--r-- | subcmds/manifest.py | 81 | ||||
| -rw-r--r-- | subcmds/overview.py | 80 | ||||
| -rw-r--r-- | subcmds/rebase.py | 23 | ||||
| -rw-r--r-- | subcmds/start.py | 5 | ||||
| -rw-r--r-- | subcmds/status.py | 86 | ||||
| -rw-r--r-- | subcmds/sync.py | 200 | ||||
| -rw-r--r-- | subcmds/upload.py | 96 | ||||
| -rw-r--r-- | subcmds/version.py | 8 |
17 files changed, 778 insertions, 232 deletions
diff --git a/subcmds/abandon.py b/subcmds/abandon.py index 8af61327..42abb2ff 100644 --- a/subcmds/abandon.py +++ b/subcmds/abandon.py | |||
| @@ -41,21 +41,30 @@ It is equivalent to "git branch -D <branchname>". | |||
| 41 | 41 | ||
| 42 | nb = args[0] | 42 | nb = args[0] |
| 43 | err = [] | 43 | err = [] |
| 44 | success = [] | ||
| 44 | all = self.GetProjects(args[1:]) | 45 | all = self.GetProjects(args[1:]) |
| 45 | 46 | ||
| 46 | pm = Progress('Abandon %s' % nb, len(all)) | 47 | pm = Progress('Abandon %s' % nb, len(all)) |
| 47 | for project in all: | 48 | for project in all: |
| 48 | pm.update() | 49 | pm.update() |
| 49 | if not project.AbandonBranch(nb): | 50 | |
| 50 | err.append(project) | 51 | status = project.AbandonBranch(nb) |
| 52 | if status is not None: | ||
| 53 | if status: | ||
| 54 | success.append(project) | ||
| 55 | else: | ||
| 56 | err.append(project) | ||
| 51 | pm.end() | 57 | pm.end() |
| 52 | 58 | ||
| 53 | if err: | 59 | if err: |
| 54 | if len(err) == len(all): | 60 | for p in err: |
| 55 | print >>sys.stderr, 'error: no project has branch %s' % nb | 61 | print >>sys.stderr,\ |
| 56 | else: | 62 | "error: %s/: cannot abandon %s" \ |
| 57 | for p in err: | 63 | % (p.relpath, nb) |
| 58 | print >>sys.stderr,\ | 64 | sys.exit(1) |
| 59 | "error: %s/: cannot abandon %s" \ | 65 | elif not success: |
| 60 | % (p.relpath, nb) | 66 | print >>sys.stderr, 'error: no project has branch %s' % nb |
| 61 | sys.exit(1) | 67 | sys.exit(1) |
| 68 | else: | ||
| 69 | print >>sys.stderr, 'Abandoned in %d project(s):\n %s' % ( | ||
| 70 | len(success), '\n '.join(p.relpath for p in success)) | ||
diff --git a/subcmds/checkout.py b/subcmds/checkout.py index 4198acd1..533d20e1 100644 --- a/subcmds/checkout.py +++ b/subcmds/checkout.py | |||
| @@ -38,21 +38,27 @@ The command is equivalent to: | |||
| 38 | 38 | ||
| 39 | nb = args[0] | 39 | nb = args[0] |
| 40 | err = [] | 40 | err = [] |
| 41 | success = [] | ||
| 41 | all = self.GetProjects(args[1:]) | 42 | all = self.GetProjects(args[1:]) |
| 42 | 43 | ||
| 43 | pm = Progress('Checkout %s' % nb, len(all)) | 44 | pm = Progress('Checkout %s' % nb, len(all)) |
| 44 | for project in all: | 45 | for project in all: |
| 45 | pm.update() | 46 | pm.update() |
| 46 | if not project.CheckoutBranch(nb): | 47 | |
| 47 | err.append(project) | 48 | status = project.CheckoutBranch(nb) |
| 49 | if status is not None: | ||
| 50 | if status: | ||
| 51 | success.append(project) | ||
| 52 | else: | ||
| 53 | err.append(project) | ||
| 48 | pm.end() | 54 | pm.end() |
| 49 | 55 | ||
| 50 | if err: | 56 | if err: |
| 51 | if len(err) == len(all): | 57 | for p in err: |
| 52 | print >>sys.stderr, 'error: no project has branch %s' % nb | 58 | print >>sys.stderr,\ |
| 53 | else: | 59 | "error: %s/: cannot checkout %s" \ |
| 54 | for p in err: | 60 | % (p.relpath, nb) |
| 55 | print >>sys.stderr,\ | 61 | sys.exit(1) |
| 56 | "error: %s/: cannot checkout %s" \ | 62 | elif not success: |
| 57 | % (p.relpath, nb) | 63 | print >>sys.stderr, 'error: no project has branch %s' % nb |
| 58 | sys.exit(1) | 64 | sys.exit(1) |
diff --git a/subcmds/cherry_pick.py b/subcmds/cherry_pick.py new file mode 100644 index 00000000..8da3a750 --- /dev/null +++ b/subcmds/cherry_pick.py | |||
| @@ -0,0 +1,114 @@ | |||
| 1 | # | ||
| 2 | # Copyright (C) 2010 The Android Open Source Project | ||
| 3 | # | ||
| 4 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | # you may not use this file except in compliance with the License. | ||
| 6 | # You may obtain a copy of the License at | ||
| 7 | # | ||
| 8 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | # | ||
| 10 | # Unless required by applicable law or agreed to in writing, software | ||
| 11 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | # See the License for the specific language governing permissions and | ||
| 14 | # limitations under the License. | ||
| 15 | |||
| 16 | import sys, re, string, random, os | ||
| 17 | from command import Command | ||
| 18 | from git_command import GitCommand | ||
| 19 | |||
| 20 | CHANGE_ID_RE = re.compile(r'^\s*Change-Id: I([0-9a-f]{40})\s*$') | ||
| 21 | |||
| 22 | class CherryPick(Command): | ||
| 23 | common = True | ||
| 24 | helpSummary = "Cherry-pick a change." | ||
| 25 | helpUsage = """ | ||
| 26 | %prog <sha1> | ||
| 27 | """ | ||
| 28 | helpDescription = """ | ||
| 29 | '%prog' cherry-picks a change from one branch to another. | ||
| 30 | The change id will be updated, and a reference to the old | ||
| 31 | change id will be added. | ||
| 32 | """ | ||
| 33 | |||
| 34 | def _Options(self, p): | ||
| 35 | pass | ||
| 36 | |||
| 37 | def Execute(self, opt, args): | ||
| 38 | if len(args) != 1: | ||
| 39 | self.Usage() | ||
| 40 | |||
| 41 | reference = args[0] | ||
| 42 | |||
| 43 | p = GitCommand(None, | ||
| 44 | ['rev-parse', '--verify', reference], | ||
| 45 | capture_stdout = True, | ||
| 46 | capture_stderr = True) | ||
| 47 | if p.Wait() != 0: | ||
| 48 | print >>sys.stderr, p.stderr | ||
| 49 | sys.exit(1) | ||
| 50 | sha1 = p.stdout.strip() | ||
| 51 | |||
| 52 | p = GitCommand(None, ['cat-file', 'commit', sha1], capture_stdout=True) | ||
| 53 | if p.Wait() != 0: | ||
| 54 | print >>sys.stderr, "error: Failed to retrieve old commit message" | ||
| 55 | sys.exit(1) | ||
| 56 | old_msg = self._StripHeader(p.stdout) | ||
| 57 | |||
| 58 | p = GitCommand(None, | ||
| 59 | ['cherry-pick', sha1], | ||
| 60 | capture_stdout = True, | ||
| 61 | capture_stderr = True) | ||
| 62 | status = p.Wait() | ||
| 63 | |||
| 64 | print >>sys.stdout, p.stdout | ||
| 65 | print >>sys.stderr, p.stderr | ||
| 66 | |||
| 67 | if status == 0: | ||
| 68 | # The cherry-pick was applied correctly. We just need to edit the | ||
| 69 | # commit message. | ||
| 70 | new_msg = self._Reformat(old_msg, sha1) | ||
| 71 | |||
| 72 | p = GitCommand(None, ['commit', '--amend', '-F', '-'], | ||
| 73 | provide_stdin = True, | ||
| 74 | capture_stdout = True, | ||
| 75 | capture_stderr = True) | ||
| 76 | p.stdin.write(new_msg) | ||
| 77 | if p.Wait() != 0: | ||
| 78 | print >>sys.stderr, "error: Failed to update commit message" | ||
| 79 | sys.exit(1) | ||
| 80 | |||
| 81 | else: | ||
| 82 | print >>sys.stderr, """\ | ||
| 83 | NOTE: When committing (please see above) and editing the commit message, | ||
| 84 | please remove the old Change-Id-line and add: | ||
| 85 | """ | ||
| 86 | print >>sys.stderr, self._GetReference(sha1) | ||
| 87 | print >>sys.stderr | ||
| 88 | |||
| 89 | def _IsChangeId(self, line): | ||
| 90 | return CHANGE_ID_RE.match(line) | ||
| 91 | |||
| 92 | def _GetReference(self, sha1): | ||
| 93 | return "(cherry picked from commit %s)" % sha1 | ||
| 94 | |||
| 95 | def _StripHeader(self, commit_msg): | ||
| 96 | lines = commit_msg.splitlines() | ||
| 97 | return "\n".join(lines[lines.index("")+1:]) | ||
| 98 | |||
| 99 | def _Reformat(self, old_msg, sha1): | ||
| 100 | new_msg = [] | ||
| 101 | |||
| 102 | for line in old_msg.splitlines(): | ||
| 103 | if not self._IsChangeId(line): | ||
| 104 | new_msg.append(line) | ||
| 105 | |||
| 106 | # Add a blank line between the message and the change id/reference | ||
| 107 | try: | ||
| 108 | if new_msg[-1].strip() != "": | ||
| 109 | new_msg.append("") | ||
| 110 | except IndexError: | ||
| 111 | pass | ||
| 112 | |||
| 113 | new_msg.append(self._GetReference(sha1)) | ||
| 114 | return "\n".join(new_msg) | ||
diff --git a/subcmds/diff.py b/subcmds/diff.py index e0247140..f233f690 100644 --- a/subcmds/diff.py +++ b/subcmds/diff.py | |||
| @@ -20,8 +20,21 @@ class Diff(PagedCommand): | |||
| 20 | helpSummary = "Show changes between commit and working tree" | 20 | helpSummary = "Show changes between commit and working tree" |
| 21 | helpUsage = """ | 21 | helpUsage = """ |
| 22 | %prog [<project>...] | 22 | %prog [<project>...] |
| 23 | |||
| 24 | The -u option causes '%prog' to generate diff output with file paths | ||
| 25 | relative to the repository root, so the output can be applied | ||
| 26 | to the Unix 'patch' command. | ||
| 23 | """ | 27 | """ |
| 24 | 28 | ||
| 29 | def _Options(self, p): | ||
| 30 | def cmd(option, opt_str, value, parser): | ||
| 31 | setattr(parser.values, option.dest, list(parser.rargs)) | ||
| 32 | while parser.rargs: | ||
| 33 | del parser.rargs[0] | ||
| 34 | p.add_option('-u', '--absolute', | ||
| 35 | dest='absolute', action='store_true', | ||
| 36 | help='Paths are relative to the repository root') | ||
| 37 | |||
| 25 | def Execute(self, opt, args): | 38 | def Execute(self, opt, args): |
| 26 | for project in self.GetProjects(args): | 39 | for project in self.GetProjects(args): |
| 27 | project.PrintWorkTreeDiff() | 40 | project.PrintWorkTreeDiff(opt.absolute) |
diff --git a/subcmds/download.py b/subcmds/download.py index 61eadd54..0ea45c3f 100644 --- a/subcmds/download.py +++ b/subcmds/download.py | |||
| @@ -33,7 +33,15 @@ makes it available in your project's local working directory. | |||
| 33 | """ | 33 | """ |
| 34 | 34 | ||
| 35 | def _Options(self, p): | 35 | def _Options(self, p): |
| 36 | pass | 36 | p.add_option('-c','--cherry-pick', |
| 37 | dest='cherrypick', action='store_true', | ||
| 38 | help="cherry-pick instead of checkout") | ||
| 39 | p.add_option('-r','--revert', | ||
| 40 | dest='revert', action='store_true', | ||
| 41 | help="revert instead of checkout") | ||
| 42 | p.add_option('-f','--ff-only', | ||
| 43 | dest='ffonly', action='store_true', | ||
| 44 | help="force fast-forward merge") | ||
| 37 | 45 | ||
| 38 | def _ParseChangeIds(self, args): | 46 | def _ParseChangeIds(self, args): |
| 39 | if not args: | 47 | if not args: |
| @@ -66,7 +74,7 @@ makes it available in your project's local working directory. | |||
| 66 | % (project.name, change_id, ps_id) | 74 | % (project.name, change_id, ps_id) |
| 67 | sys.exit(1) | 75 | sys.exit(1) |
| 68 | 76 | ||
| 69 | if not dl.commits: | 77 | if not opt.revert and not dl.commits: |
| 70 | print >>sys.stderr, \ | 78 | print >>sys.stderr, \ |
| 71 | '[%s] change %d/%d has already been merged' \ | 79 | '[%s] change %d/%d has already been merged' \ |
| 72 | % (project.name, change_id, ps_id) | 80 | % (project.name, change_id, ps_id) |
| @@ -78,4 +86,11 @@ makes it available in your project's local working directory. | |||
| 78 | % (project.name, change_id, ps_id, len(dl.commits)) | 86 | % (project.name, change_id, ps_id, len(dl.commits)) |
| 79 | for c in dl.commits: | 87 | for c in dl.commits: |
| 80 | print >>sys.stderr, ' %s' % (c) | 88 | print >>sys.stderr, ' %s' % (c) |
| 81 | project._Checkout(dl.commit) | 89 | if opt.cherrypick: |
| 90 | project._CherryPick(dl.commit) | ||
| 91 | elif opt.revert: | ||
| 92 | project._Revert(dl.commit) | ||
| 93 | elif opt.ffonly: | ||
| 94 | project._FastForward(dl.commit, ffonly=True) | ||
| 95 | else: | ||
| 96 | project._Checkout(dl.commit) | ||
diff --git a/subcmds/forall.py b/subcmds/forall.py index d3e70ae1..9436f4e5 100644 --- a/subcmds/forall.py +++ b/subcmds/forall.py | |||
| @@ -82,6 +82,11 @@ revision to a locally executed git command, use REPO_LREV. | |||
| 82 | REPO_RREV is the name of the revision from the manifest, exactly | 82 | REPO_RREV is the name of the revision from the manifest, exactly |
| 83 | as written in the manifest. | 83 | as written in the manifest. |
| 84 | 84 | ||
| 85 | REPO__* are any extra environment variables, specified by the | ||
| 86 | "annotation" element under any project element. This can be useful | ||
| 87 | for differentiating trees based on user-specific criteria, or simply | ||
| 88 | annotating tree details. | ||
| 89 | |||
| 85 | shell positional arguments ($1, $2, .., $#) are set to any arguments | 90 | shell positional arguments ($1, $2, .., $#) are set to any arguments |
| 86 | following <command>. | 91 | following <command>. |
| 87 | 92 | ||
| @@ -162,6 +167,8 @@ terminal and are not redirected. | |||
| 162 | setenv('REPO_REMOTE', project.remote.name) | 167 | setenv('REPO_REMOTE', project.remote.name) |
| 163 | setenv('REPO_LREV', project.GetRevisionId()) | 168 | setenv('REPO_LREV', project.GetRevisionId()) |
| 164 | setenv('REPO_RREV', project.revisionExpr) | 169 | setenv('REPO_RREV', project.revisionExpr) |
| 170 | for a in project.annotations: | ||
| 171 | setenv("REPO__%s" % (a.name), a.value) | ||
| 165 | 172 | ||
| 166 | if mirror: | 173 | if mirror: |
| 167 | setenv('GIT_DIR', project.gitdir) | 174 | setenv('GIT_DIR', project.gitdir) |
diff --git a/subcmds/help.py b/subcmds/help.py index e2f3074c..0df3c14b 100644 --- a/subcmds/help.py +++ b/subcmds/help.py | |||
| @@ -165,7 +165,7 @@ See 'repo help --all' for a complete list of recognized commands. | |||
| 165 | print >>sys.stderr, "repo: '%s' is not a repo command." % name | 165 | print >>sys.stderr, "repo: '%s' is not a repo command." % name |
| 166 | sys.exit(1) | 166 | sys.exit(1) |
| 167 | 167 | ||
| 168 | cmd.repodir = self.repodir | 168 | cmd.manifest = self.manifest |
| 169 | self._PrintCommandHelp(cmd) | 169 | self._PrintCommandHelp(cmd) |
| 170 | 170 | ||
| 171 | else: | 171 | else: |
diff --git a/subcmds/init.py b/subcmds/init.py index 2ca4e163..a758fbb1 100644 --- a/subcmds/init.py +++ b/subcmds/init.py | |||
| @@ -14,16 +14,17 @@ | |||
| 14 | # limitations under the License. | 14 | # limitations under the License. |
| 15 | 15 | ||
| 16 | import os | 16 | import os |
| 17 | import platform | ||
| 18 | import re | ||
| 19 | import shutil | ||
| 17 | import sys | 20 | import sys |
| 18 | 21 | ||
| 19 | from color import Coloring | 22 | from color import Coloring |
| 20 | from command import InteractiveCommand, MirrorSafeCommand | 23 | from command import InteractiveCommand, MirrorSafeCommand |
| 21 | from error import ManifestParseError | 24 | from error import ManifestParseError |
| 22 | from project import SyncBuffer | 25 | from project import SyncBuffer |
| 26 | from git_config import GitConfig | ||
| 23 | from git_command import git_require, MIN_GIT_VERSION | 27 | from git_command import git_require, MIN_GIT_VERSION |
| 24 | from manifest_submodule import SubmoduleManifest | ||
| 25 | from manifest_xml import XmlManifest | ||
| 26 | from subcmds.sync import _ReloadManifest | ||
| 27 | 28 | ||
| 28 | class Init(InteractiveCommand, MirrorSafeCommand): | 29 | class Init(InteractiveCommand, MirrorSafeCommand): |
| 29 | common = True | 30 | common = True |
| @@ -75,21 +76,27 @@ to update the working directory files. | |||
| 75 | g.add_option('-b', '--manifest-branch', | 76 | g.add_option('-b', '--manifest-branch', |
| 76 | dest='manifest_branch', | 77 | dest='manifest_branch', |
| 77 | help='manifest branch or revision', metavar='REVISION') | 78 | help='manifest branch or revision', metavar='REVISION') |
| 78 | g.add_option('-o', '--origin', | 79 | g.add_option('-m', '--manifest-name', |
| 79 | dest='manifest_origin', | 80 | dest='manifest_name', default='default.xml', |
| 80 | help="use REMOTE instead of 'origin' to track upstream", | 81 | help='initial manifest file', metavar='NAME.xml') |
| 81 | metavar='REMOTE') | ||
| 82 | if isinstance(self.manifest, XmlManifest) \ | ||
| 83 | or not self.manifest.manifestProject.Exists: | ||
| 84 | g.add_option('-m', '--manifest-name', | ||
| 85 | dest='manifest_name', default='default.xml', | ||
| 86 | help='initial manifest file', metavar='NAME.xml') | ||
| 87 | g.add_option('--mirror', | 82 | g.add_option('--mirror', |
| 88 | dest='mirror', action='store_true', | 83 | dest='mirror', action='store_true', |
| 89 | help='mirror the forrest') | 84 | help='mirror the forrest') |
| 90 | g.add_option('--reference', | 85 | g.add_option('--reference', |
| 91 | dest='reference', | 86 | dest='reference', |
| 92 | help='location of mirror directory', metavar='DIR') | 87 | help='location of mirror directory', metavar='DIR') |
| 88 | g.add_option('--depth', type='int', default=None, | ||
| 89 | dest='depth', | ||
| 90 | help='create a shallow clone with given depth; see git clone') | ||
| 91 | g.add_option('-g', '--groups', | ||
| 92 | dest='groups', default='default', | ||
| 93 | help='restrict manifest projects to ones with a specified group', | ||
| 94 | metavar='GROUP') | ||
| 95 | g.add_option('-p', '--platform', | ||
| 96 | dest='platform', default='auto', | ||
| 97 | help='restrict manifest projects to ones with a specified' | ||
| 98 | 'platform group [auto|all|none|linux|darwin|...]', | ||
| 99 | metavar='PLATFORM') | ||
| 93 | 100 | ||
| 94 | # Tool | 101 | # Tool |
| 95 | g = p.add_option_group('repo Version options') | 102 | g = p.add_option_group('repo Version options') |
| @@ -103,91 +110,94 @@ to update the working directory files. | |||
| 103 | dest='no_repo_verify', action='store_true', | 110 | dest='no_repo_verify', action='store_true', |
| 104 | help='do not verify repo source code') | 111 | help='do not verify repo source code') |
| 105 | 112 | ||
| 106 | def _ApplyOptions(self, opt, is_new): | 113 | # Other |
| 114 | g = p.add_option_group('Other options') | ||
| 115 | g.add_option('--config-name', | ||
| 116 | dest='config_name', action="store_true", default=False, | ||
| 117 | help='Always prompt for name/e-mail') | ||
| 118 | |||
| 119 | def _SyncManifest(self, opt): | ||
| 107 | m = self.manifest.manifestProject | 120 | m = self.manifest.manifestProject |
| 121 | is_new = not m.Exists | ||
| 108 | 122 | ||
| 109 | if is_new: | 123 | if is_new: |
| 110 | if opt.manifest_origin: | 124 | if not opt.manifest_url: |
| 111 | m.remote.name = opt.manifest_origin | 125 | print >>sys.stderr, 'fatal: manifest url (-u) is required.' |
| 126 | sys.exit(1) | ||
| 127 | |||
| 128 | if not opt.quiet: | ||
| 129 | print >>sys.stderr, 'Get %s' \ | ||
| 130 | % GitConfig.ForUser().UrlInsteadOf(opt.manifest_url) | ||
| 131 | m._InitGitDir() | ||
| 112 | 132 | ||
| 113 | if opt.manifest_branch: | 133 | if opt.manifest_branch: |
| 114 | m.revisionExpr = opt.manifest_branch | 134 | m.revisionExpr = opt.manifest_branch |
| 115 | else: | 135 | else: |
| 116 | m.revisionExpr = 'refs/heads/master' | 136 | m.revisionExpr = 'refs/heads/master' |
| 117 | else: | 137 | else: |
| 118 | if opt.manifest_origin: | ||
| 119 | print >>sys.stderr, 'fatal: cannot change origin name' | ||
| 120 | sys.exit(1) | ||
| 121 | |||
| 122 | if opt.manifest_branch: | 138 | if opt.manifest_branch: |
| 123 | m.revisionExpr = opt.manifest_branch | 139 | m.revisionExpr = opt.manifest_branch |
| 124 | else: | 140 | else: |
| 125 | m.PreSync() | 141 | m.PreSync() |
| 126 | 142 | ||
| 127 | def _SyncManifest(self, opt): | ||
| 128 | m = self.manifest.manifestProject | ||
| 129 | is_new = not m.Exists | ||
| 130 | |||
| 131 | if is_new: | ||
| 132 | if not opt.manifest_url: | ||
| 133 | print >>sys.stderr, 'fatal: manifest url (-u) is required.' | ||
| 134 | sys.exit(1) | ||
| 135 | |||
| 136 | if not opt.quiet: | ||
| 137 | print >>sys.stderr, 'Getting manifest ...' | ||
| 138 | print >>sys.stderr, ' from %s' % opt.manifest_url | ||
| 139 | m._InitGitDir() | ||
| 140 | |||
| 141 | self._ApplyOptions(opt, is_new) | ||
| 142 | if opt.manifest_url: | 143 | if opt.manifest_url: |
| 143 | r = m.GetRemote(m.remote.name) | 144 | r = m.GetRemote(m.remote.name) |
| 144 | r.url = opt.manifest_url | 145 | r.url = opt.manifest_url |
| 145 | r.ResetFetch() | 146 | r.ResetFetch() |
| 146 | r.Save() | 147 | r.Save() |
| 147 | 148 | ||
| 149 | groups = re.split('[,\s]+', opt.groups) | ||
| 150 | all_platforms = ['linux', 'darwin'] | ||
| 151 | platformize = lambda x: 'platform-' + x | ||
| 152 | if opt.platform == 'auto': | ||
| 153 | if (not opt.mirror and | ||
| 154 | not m.config.GetString('repo.mirror') == 'true'): | ||
| 155 | groups.append(platformize(platform.system().lower())) | ||
| 156 | elif opt.platform == 'all': | ||
| 157 | groups.extend(map(platformize, all_platforms)) | ||
| 158 | elif opt.platform in all_platforms: | ||
| 159 | groups.extend(platformize(opt.platform)) | ||
| 160 | elif opt.platform != 'none': | ||
| 161 | print >>sys.stderr, 'fatal: invalid platform flag' | ||
| 162 | sys.exit(1) | ||
| 163 | |||
| 164 | groups = [x for x in groups if x] | ||
| 165 | groupstr = ','.join(groups) | ||
| 166 | if opt.platform == 'auto' and groupstr == 'default,platform-' + platform.system().lower(): | ||
| 167 | groupstr = None | ||
| 168 | m.config.SetString('manifest.groups', groupstr) | ||
| 169 | |||
| 148 | if opt.reference: | 170 | if opt.reference: |
| 149 | m.config.SetString('repo.reference', opt.reference) | 171 | m.config.SetString('repo.reference', opt.reference) |
| 150 | 172 | ||
| 151 | if opt.mirror: | 173 | if opt.mirror: |
| 152 | if is_new: | 174 | if is_new: |
| 153 | m.config.SetString('repo.mirror', 'true') | 175 | m.config.SetString('repo.mirror', 'true') |
| 154 | m.config.ClearCache() | ||
| 155 | else: | 176 | else: |
| 156 | print >>sys.stderr, 'fatal: --mirror not supported on existing client' | 177 | print >>sys.stderr, 'fatal: --mirror not supported on existing client' |
| 157 | sys.exit(1) | 178 | sys.exit(1) |
| 158 | 179 | ||
| 159 | if not m.Sync_NetworkHalf(): | 180 | if not m.Sync_NetworkHalf(is_new=is_new): |
| 160 | r = m.GetRemote(m.remote.name) | 181 | r = m.GetRemote(m.remote.name) |
| 161 | print >>sys.stderr, 'fatal: cannot obtain manifest %s' % r.url | 182 | print >>sys.stderr, 'fatal: cannot obtain manifest %s' % r.url |
| 162 | sys.exit(1) | ||
| 163 | 183 | ||
| 164 | if is_new and SubmoduleManifest.IsBare(m): | 184 | # Better delete the manifest git dir if we created it; otherwise next |
| 165 | new = self.GetManifest(reparse=True, type=SubmoduleManifest) | 185 | # time (when user fixes problems) we won't go through the "is_new" logic. |
| 166 | if m.gitdir != new.manifestProject.gitdir: | 186 | if is_new: |
| 167 | os.rename(m.gitdir, new.manifestProject.gitdir) | 187 | shutil.rmtree(m.gitdir) |
| 168 | new = self.GetManifest(reparse=True, type=SubmoduleManifest) | 188 | sys.exit(1) |
| 169 | m = new.manifestProject | ||
| 170 | self._ApplyOptions(opt, is_new) | ||
| 171 | 189 | ||
| 172 | if not is_new: | 190 | if opt.manifest_branch: |
| 173 | # Force the manifest to load if it exists, the old graph | 191 | m.MetaBranchSwitch(opt.manifest_branch) |
| 174 | # may be needed inside of _ReloadManifest(). | ||
| 175 | # | ||
| 176 | self.manifest.projects | ||
| 177 | 192 | ||
| 178 | syncbuf = SyncBuffer(m.config) | 193 | syncbuf = SyncBuffer(m.config) |
| 179 | m.Sync_LocalHalf(syncbuf) | 194 | m.Sync_LocalHalf(syncbuf) |
| 180 | syncbuf.Finish() | 195 | syncbuf.Finish() |
| 181 | 196 | ||
| 182 | if isinstance(self.manifest, XmlManifest): | 197 | if is_new or m.CurrentBranch is None: |
| 183 | self._LinkManifest(opt.manifest_name) | 198 | if not m.StartBranch('default'): |
| 184 | _ReloadManifest(self) | 199 | print >>sys.stderr, 'fatal: cannot create default in manifest' |
| 185 | 200 | sys.exit(1) | |
| 186 | self._ApplyOptions(opt, is_new) | ||
| 187 | |||
| 188 | if not self.manifest.InitBranch(): | ||
| 189 | print >>sys.stderr, 'fatal: cannot create branch in manifest' | ||
| 190 | sys.exit(1) | ||
| 191 | 201 | ||
| 192 | def _LinkManifest(self, name): | 202 | def _LinkManifest(self, name): |
| 193 | if not name: | 203 | if not name: |
| @@ -210,6 +220,24 @@ to update the working directory files. | |||
| 210 | return value | 220 | return value |
| 211 | return a | 221 | return a |
| 212 | 222 | ||
| 223 | def _ShouldConfigureUser(self): | ||
| 224 | gc = self.manifest.globalConfig | ||
| 225 | mp = self.manifest.manifestProject | ||
| 226 | |||
| 227 | # If we don't have local settings, get from global. | ||
| 228 | if not mp.config.Has('user.name') or not mp.config.Has('user.email'): | ||
| 229 | if not gc.Has('user.name') or not gc.Has('user.email'): | ||
| 230 | return True | ||
| 231 | |||
| 232 | mp.config.SetString('user.name', gc.GetString('user.name')) | ||
| 233 | mp.config.SetString('user.email', gc.GetString('user.email')) | ||
| 234 | |||
| 235 | print '' | ||
| 236 | print 'Your identity is: %s <%s>' % (mp.config.GetString('user.name'), | ||
| 237 | mp.config.GetString('user.email')) | ||
| 238 | print 'If you want to change this, please re-run \'repo init\' with --config-name' | ||
| 239 | return False | ||
| 240 | |||
| 213 | def _ConfigureUser(self): | 241 | def _ConfigureUser(self): |
| 214 | mp = self.manifest.manifestProject | 242 | mp = self.manifest.manifestProject |
| 215 | 243 | ||
| @@ -220,7 +248,7 @@ to update the working directory files. | |||
| 220 | 248 | ||
| 221 | print '' | 249 | print '' |
| 222 | print 'Your identity is: %s <%s>' % (name, email) | 250 | print 'Your identity is: %s <%s>' % (name, email) |
| 223 | sys.stdout.write('is this correct [y/n]? ') | 251 | sys.stdout.write('is this correct [y/N]? ') |
| 224 | a = sys.stdin.readline().strip() | 252 | a = sys.stdin.readline().strip() |
| 225 | if a in ('yes', 'y', 't', 'true'): | 253 | if a in ('yes', 'y', 't', 'true'): |
| 226 | break | 254 | break |
| @@ -262,19 +290,42 @@ to update the working directory files. | |||
| 262 | out.printer(fg='black', attr=c)(' %-6s ', c) | 290 | out.printer(fg='black', attr=c)(' %-6s ', c) |
| 263 | out.nl() | 291 | out.nl() |
| 264 | 292 | ||
| 265 | sys.stdout.write('Enable color display in this user account (y/n)? ') | 293 | sys.stdout.write('Enable color display in this user account (y/N)? ') |
| 266 | a = sys.stdin.readline().strip().lower() | 294 | a = sys.stdin.readline().strip().lower() |
| 267 | if a in ('y', 'yes', 't', 'true', 'on'): | 295 | if a in ('y', 'yes', 't', 'true', 'on'): |
| 268 | gc.SetString('color.ui', 'auto') | 296 | gc.SetString('color.ui', 'auto') |
| 269 | 297 | ||
| 298 | def _ConfigureDepth(self, opt): | ||
| 299 | """Configure the depth we'll sync down. | ||
| 300 | |||
| 301 | Args: | ||
| 302 | opt: Options from optparse. We care about opt.depth. | ||
| 303 | """ | ||
| 304 | # Opt.depth will be non-None if user actually passed --depth to repo init. | ||
| 305 | if opt.depth is not None: | ||
| 306 | if opt.depth > 0: | ||
| 307 | # Positive values will set the depth. | ||
| 308 | depth = str(opt.depth) | ||
| 309 | else: | ||
| 310 | # Negative numbers will clear the depth; passing None to SetString | ||
| 311 | # will do that. | ||
| 312 | depth = None | ||
| 313 | |||
| 314 | # We store the depth in the main manifest project. | ||
| 315 | self.manifest.manifestProject.config.SetString('repo.depth', depth) | ||
| 316 | |||
| 270 | def Execute(self, opt, args): | 317 | def Execute(self, opt, args): |
| 271 | git_require(MIN_GIT_VERSION, fail=True) | 318 | git_require(MIN_GIT_VERSION, fail=True) |
| 272 | self._SyncManifest(opt) | 319 | self._SyncManifest(opt) |
| 320 | self._LinkManifest(opt.manifest_name) | ||
| 273 | 321 | ||
| 274 | if os.isatty(0) and os.isatty(1) and not self.manifest.IsMirror: | 322 | if os.isatty(0) and os.isatty(1) and not self.manifest.IsMirror: |
| 275 | self._ConfigureUser() | 323 | if opt.config_name or self._ShouldConfigureUser(): |
| 324 | self._ConfigureUser() | ||
| 276 | self._ConfigureColor() | 325 | self._ConfigureColor() |
| 277 | 326 | ||
| 327 | self._ConfigureDepth(opt) | ||
| 328 | |||
| 278 | if self.manifest.IsMirror: | 329 | if self.manifest.IsMirror: |
| 279 | type = 'mirror ' | 330 | type = 'mirror ' |
| 280 | else: | 331 | else: |
diff --git a/subcmds/list.py b/subcmds/list.py new file mode 100644 index 00000000..2be82570 --- /dev/null +++ b/subcmds/list.py | |||
| @@ -0,0 +1,48 @@ | |||
| 1 | # | ||
| 2 | # Copyright (C) 2011 The Android Open Source Project | ||
| 3 | # | ||
| 4 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | # you may not use this file except in compliance with the License. | ||
| 6 | # You may obtain a copy of the License at | ||
| 7 | # | ||
| 8 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | # | ||
| 10 | # Unless required by applicable law or agreed to in writing, software | ||
| 11 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | # See the License for the specific language governing permissions and | ||
| 14 | # limitations under the License. | ||
| 15 | |||
| 16 | from command import Command, MirrorSafeCommand | ||
| 17 | |||
| 18 | class List(Command, MirrorSafeCommand): | ||
| 19 | common = True | ||
| 20 | helpSummary = "List projects and their associated directories" | ||
| 21 | helpUsage = """ | ||
| 22 | %prog [<project>...] | ||
| 23 | """ | ||
| 24 | helpDescription = """ | ||
| 25 | List all projects; pass '.' to list the project for the cwd. | ||
| 26 | |||
| 27 | This is similar to running: repo forall -c 'echo "$REPO_PATH : $REPO_PROJECT"'. | ||
| 28 | """ | ||
| 29 | |||
| 30 | def Execute(self, opt, args): | ||
| 31 | """List all projects and the associated directories. | ||
| 32 | |||
| 33 | This may be possible to do with 'repo forall', but repo newbies have | ||
| 34 | trouble figuring that out. The idea here is that it should be more | ||
| 35 | discoverable. | ||
| 36 | |||
| 37 | Args: | ||
| 38 | opt: The options. We don't take any. | ||
| 39 | args: Positional args. Can be a list of projects to list, or empty. | ||
| 40 | """ | ||
| 41 | projects = self.GetProjects(args) | ||
| 42 | |||
| 43 | lines = [] | ||
| 44 | for project in projects: | ||
| 45 | lines.append("%s : %s" % (project.relpath, project.name)) | ||
| 46 | |||
| 47 | lines.sort() | ||
| 48 | print '\n'.join(lines) | ||
diff --git a/subcmds/manifest.py b/subcmds/manifest.py index dcd3df17..4374a9d0 100644 --- a/subcmds/manifest.py +++ b/subcmds/manifest.py | |||
| @@ -17,25 +17,14 @@ import os | |||
| 17 | import sys | 17 | import sys |
| 18 | 18 | ||
| 19 | from command import PagedCommand | 19 | from command import PagedCommand |
| 20 | from manifest_submodule import SubmoduleManifest | ||
| 21 | from manifest_xml import XmlManifest | ||
| 22 | |||
| 23 | def _doc(name): | ||
| 24 | r = os.path.dirname(__file__) | ||
| 25 | r = os.path.dirname(r) | ||
| 26 | fd = open(os.path.join(r, 'docs', name)) | ||
| 27 | try: | ||
| 28 | return fd.read() | ||
| 29 | finally: | ||
| 30 | fd.close() | ||
| 31 | 20 | ||
| 32 | class Manifest(PagedCommand): | 21 | class Manifest(PagedCommand): |
| 33 | common = False | 22 | common = False |
| 34 | helpSummary = "Manifest inspection utility" | 23 | helpSummary = "Manifest inspection utility" |
| 35 | helpUsage = """ | 24 | helpUsage = """ |
| 36 | %prog [options] | 25 | %prog [-o {-|NAME.xml} [-r]] |
| 37 | """ | 26 | """ |
| 38 | _xmlHelp = """ | 27 | _helpDescription = """ |
| 39 | 28 | ||
| 40 | With the -o option, exports the current manifest for inspection. | 29 | With the -o option, exports the current manifest for inspection. |
| 41 | The manifest and (if present) local_manifest.xml are combined | 30 | The manifest and (if present) local_manifest.xml are combined |
| @@ -46,30 +35,23 @@ in a Git repository for use during future 'repo init' invocations. | |||
| 46 | 35 | ||
| 47 | @property | 36 | @property |
| 48 | def helpDescription(self): | 37 | def helpDescription(self): |
| 49 | help = '' | 38 | help = self._helpDescription + '\n' |
| 50 | if isinstance(self.manifest, XmlManifest): | 39 | r = os.path.dirname(__file__) |
| 51 | help += self._xmlHelp + '\n' + _doc('manifest_xml.txt') | 40 | r = os.path.dirname(r) |
| 52 | if isinstance(self.manifest, SubmoduleManifest): | 41 | fd = open(os.path.join(r, 'docs', 'manifest-format.txt')) |
| 53 | help += _doc('manifest_submodule.txt') | 42 | for line in fd: |
| 43 | help += line | ||
| 44 | fd.close() | ||
| 54 | return help | 45 | return help |
| 55 | 46 | ||
| 56 | def _Options(self, p): | 47 | def _Options(self, p): |
| 57 | if isinstance(self.manifest, XmlManifest): | 48 | p.add_option('-r', '--revision-as-HEAD', |
| 58 | p.add_option('--upgrade', | 49 | dest='peg_rev', action='store_true', |
| 59 | dest='upgrade', action='store_true', | 50 | help='Save revisions as current HEAD') |
| 60 | help='Upgrade XML manifest to submodule') | 51 | p.add_option('-o', '--output-file', |
| 61 | p.add_option('-r', '--revision-as-HEAD', | 52 | dest='output_file', |
| 62 | dest='peg_rev', action='store_true', | 53 | help='File to save the manifest to', |
| 63 | help='Save revisions as current HEAD') | 54 | metavar='-|NAME.xml') |
| 64 | p.add_option('-o', '--output-file', | ||
| 65 | dest='output_file', | ||
| 66 | help='File to save the manifest to', | ||
| 67 | metavar='-|NAME.xml') | ||
| 68 | |||
| 69 | def WantPager(self, opt): | ||
| 70 | if isinstance(self.manifest, XmlManifest) and opt.upgrade: | ||
| 71 | return False | ||
| 72 | return True | ||
| 73 | 55 | ||
| 74 | def _Output(self, opt): | 56 | def _Output(self, opt): |
| 75 | if opt.output_file == '-': | 57 | if opt.output_file == '-': |
| @@ -82,38 +64,13 @@ in a Git repository for use during future 'repo init' invocations. | |||
| 82 | if opt.output_file != '-': | 64 | if opt.output_file != '-': |
| 83 | print >>sys.stderr, 'Saved manifest to %s' % opt.output_file | 65 | print >>sys.stderr, 'Saved manifest to %s' % opt.output_file |
| 84 | 66 | ||
| 85 | def _Upgrade(self): | ||
| 86 | old = self.manifest | ||
| 87 | |||
| 88 | if isinstance(old, SubmoduleManifest): | ||
| 89 | print >>sys.stderr, 'error: already upgraded' | ||
| 90 | sys.exit(1) | ||
| 91 | |||
| 92 | old._Load() | ||
| 93 | for p in old.projects.values(): | ||
| 94 | if not os.path.exists(p.gitdir) \ | ||
| 95 | or not os.path.exists(p.worktree): | ||
| 96 | print >>sys.stderr, 'fatal: project "%s" missing' % p.relpath | ||
| 97 | sys.exit(1) | ||
| 98 | |||
| 99 | new = SubmoduleManifest(old.repodir) | ||
| 100 | new.FromXml_Local_1(old, checkout=False) | ||
| 101 | new.FromXml_Definition(old) | ||
| 102 | new.FromXml_Local_2(old) | ||
| 103 | print >>sys.stderr, 'upgraded manifest; commit result manually' | ||
| 104 | |||
| 105 | def Execute(self, opt, args): | 67 | def Execute(self, opt, args): |
| 106 | if args: | 68 | if args: |
| 107 | self.Usage() | 69 | self.Usage() |
| 108 | 70 | ||
| 109 | if isinstance(self.manifest, XmlManifest): | 71 | if opt.output_file is not None: |
| 110 | if opt.upgrade: | 72 | self._Output(opt) |
| 111 | self._Upgrade() | 73 | return |
| 112 | return | ||
| 113 | |||
| 114 | if opt.output_file is not None: | ||
| 115 | self._Output(opt) | ||
| 116 | return | ||
| 117 | 74 | ||
| 118 | print >>sys.stderr, 'error: no operation to perform' | 75 | print >>sys.stderr, 'error: no operation to perform' |
| 119 | print >>sys.stderr, 'error: see repo help manifest' | 76 | print >>sys.stderr, 'error: see repo help manifest' |
diff --git a/subcmds/overview.py b/subcmds/overview.py new file mode 100644 index 00000000..96fa93d8 --- /dev/null +++ b/subcmds/overview.py | |||
| @@ -0,0 +1,80 @@ | |||
| 1 | # | ||
| 2 | # Copyright (C) 2012 The Android Open Source Project | ||
| 3 | # | ||
| 4 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | # you may not use this file except in compliance with the License. | ||
| 6 | # You may obtain a copy of the License at | ||
| 7 | # | ||
| 8 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | # | ||
| 10 | # Unless required by applicable law or agreed to in writing, software | ||
| 11 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | # See the License for the specific language governing permissions and | ||
| 14 | # limitations under the License. | ||
| 15 | |||
| 16 | from color import Coloring | ||
| 17 | from command import PagedCommand | ||
| 18 | |||
| 19 | |||
| 20 | class Overview(PagedCommand): | ||
| 21 | common = True | ||
| 22 | helpSummary = "Display overview of unmerged project branches" | ||
| 23 | helpUsage = """ | ||
| 24 | %prog [--current-branch] [<project>...] | ||
| 25 | """ | ||
| 26 | helpDescription = """ | ||
| 27 | The '%prog' command is used to display an overview of the projects branches, | ||
| 28 | and list any local commits that have not yet been merged into the project. | ||
| 29 | |||
| 30 | The -b/--current-branch option can be used to restrict the output to only | ||
| 31 | branches currently checked out in each project. By default, all branches | ||
| 32 | are displayed. | ||
| 33 | """ | ||
| 34 | |||
| 35 | def _Options(self, p): | ||
| 36 | p.add_option('-b', '--current-branch', | ||
| 37 | dest="current_branch", action="store_true", | ||
| 38 | help="Consider only checked out branches") | ||
| 39 | |||
| 40 | def Execute(self, opt, args): | ||
| 41 | all = [] | ||
| 42 | for project in self.GetProjects(args): | ||
| 43 | br = [project.GetUploadableBranch(x) | ||
| 44 | for x in project.GetBranches().keys()] | ||
| 45 | br = [x for x in br if x] | ||
| 46 | if opt.current_branch: | ||
| 47 | br = [x for x in br if x.name == project.CurrentBranch] | ||
| 48 | all.extend(br) | ||
| 49 | |||
| 50 | if not all: | ||
| 51 | return | ||
| 52 | |||
| 53 | class Report(Coloring): | ||
| 54 | def __init__(self, config): | ||
| 55 | Coloring.__init__(self, config, 'status') | ||
| 56 | self.project = self.printer('header', attr='bold') | ||
| 57 | |||
| 58 | out = Report(all[0].project.config) | ||
| 59 | out.project('Projects Overview') | ||
| 60 | out.nl() | ||
| 61 | |||
| 62 | project = None | ||
| 63 | |||
| 64 | for branch in all: | ||
| 65 | if project != branch.project: | ||
| 66 | project = branch.project | ||
| 67 | out.nl() | ||
| 68 | out.project('project %s/' % project.relpath) | ||
| 69 | out.nl() | ||
| 70 | |||
| 71 | commits = branch.commits | ||
| 72 | date = branch.date | ||
| 73 | print '%s %-33s (%2d commit%s, %s)' % ( | ||
| 74 | branch.name == project.CurrentBranch and '*' or ' ', | ||
| 75 | branch.name, | ||
| 76 | len(commits), | ||
| 77 | len(commits) != 1 and 's' or ' ', | ||
| 78 | date) | ||
| 79 | for commit in commits: | ||
| 80 | print '%-35s - %s' % ('', commit) | ||
diff --git a/subcmds/rebase.py b/subcmds/rebase.py index e341296d..20662b11 100644 --- a/subcmds/rebase.py +++ b/subcmds/rebase.py | |||
| @@ -17,7 +17,7 @@ import sys | |||
| 17 | 17 | ||
| 18 | from command import Command | 18 | from command import Command |
| 19 | from git_command import GitCommand | 19 | from git_command import GitCommand |
| 20 | from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB | 20 | from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M |
| 21 | from error import GitError | 21 | from error import GitError |
| 22 | 22 | ||
| 23 | class Rebase(Command): | 23 | class Rebase(Command): |
| @@ -52,6 +52,9 @@ branch but need to incorporate new upstream changes "underneath" them. | |||
| 52 | p.add_option('--whitespace', | 52 | p.add_option('--whitespace', |
| 53 | dest='whitespace', action='store', metavar='WS', | 53 | dest='whitespace', action='store', metavar='WS', |
| 54 | help='Pass --whitespace to git rebase') | 54 | help='Pass --whitespace to git rebase') |
| 55 | p.add_option('--auto-stash', | ||
| 56 | dest='auto_stash', action='store_true', | ||
| 57 | help='Stash local modifications before starting') | ||
| 55 | 58 | ||
| 56 | def Execute(self, opt, args): | 59 | def Execute(self, opt, args): |
| 57 | all = self.GetProjects(args) | 60 | all = self.GetProjects(args) |
| @@ -103,5 +106,23 @@ branch but need to incorporate new upstream changes "underneath" them. | |||
| 103 | print >>sys.stderr, '# %s: rebasing %s -> %s' % \ | 106 | print >>sys.stderr, '# %s: rebasing %s -> %s' % \ |
| 104 | (project.relpath, cb, upbranch.LocalMerge) | 107 | (project.relpath, cb, upbranch.LocalMerge) |
| 105 | 108 | ||
| 109 | needs_stash = False | ||
| 110 | if opt.auto_stash: | ||
| 111 | stash_args = ["update-index", "--refresh", "-q"] | ||
| 112 | |||
| 113 | if GitCommand(project, stash_args).Wait() != 0: | ||
| 114 | needs_stash = True | ||
| 115 | # Dirty index, requires stash... | ||
| 116 | stash_args = ["stash"] | ||
| 117 | |||
| 118 | if GitCommand(project, stash_args).Wait() != 0: | ||
| 119 | return -1 | ||
| 120 | |||
| 106 | if GitCommand(project, args).Wait() != 0: | 121 | if GitCommand(project, args).Wait() != 0: |
| 107 | return -1 | 122 | return -1 |
| 123 | |||
| 124 | if needs_stash: | ||
| 125 | stash_args.append('pop') | ||
| 126 | stash_args.append('--quiet') | ||
| 127 | if GitCommand(project, stash_args).Wait() != 0: | ||
| 128 | return -1 | ||
diff --git a/subcmds/start.py b/subcmds/start.py index ae2985d2..00885076 100644 --- a/subcmds/start.py +++ b/subcmds/start.py | |||
| @@ -15,6 +15,7 @@ | |||
| 15 | 15 | ||
| 16 | import sys | 16 | import sys |
| 17 | from command import Command | 17 | from command import Command |
| 18 | from git_config import IsId | ||
| 18 | from git_command import git | 19 | from git_command import git |
| 19 | from progress import Progress | 20 | from progress import Progress |
| 20 | 21 | ||
| @@ -56,6 +57,10 @@ revision specified in the manifest. | |||
| 56 | pm = Progress('Starting %s' % nb, len(all)) | 57 | pm = Progress('Starting %s' % nb, len(all)) |
| 57 | for project in all: | 58 | for project in all: |
| 58 | pm.update() | 59 | pm.update() |
| 60 | # If the current revision is a specific SHA1 then we can't push back | ||
| 61 | # to it so substitute the manifest default revision instead. | ||
| 62 | if IsId(project.revisionExpr): | ||
| 63 | project.revisionExpr = self.manifest.default.revisionExpr | ||
| 59 | if not project.StartBranch(nb): | 64 | if not project.StartBranch(nb): |
| 60 | err.append(project) | 65 | err.append(project) |
| 61 | pm.end() | 66 | pm.end() |
diff --git a/subcmds/status.py b/subcmds/status.py index b0d419a7..69e2dbfc 100644 --- a/subcmds/status.py +++ b/subcmds/status.py | |||
| @@ -15,6 +15,15 @@ | |||
| 15 | 15 | ||
| 16 | from command import PagedCommand | 16 | from command import PagedCommand |
| 17 | 17 | ||
| 18 | try: | ||
| 19 | import threading as _threading | ||
| 20 | except ImportError: | ||
| 21 | import dummy_threading as _threading | ||
| 22 | |||
| 23 | import itertools | ||
| 24 | import sys | ||
| 25 | import StringIO | ||
| 26 | |||
| 18 | class Status(PagedCommand): | 27 | class Status(PagedCommand): |
| 19 | common = True | 28 | common = True |
| 20 | helpSummary = "Show the working tree status" | 29 | helpSummary = "Show the working tree status" |
| @@ -27,6 +36,9 @@ and the most recent commit on this branch (HEAD), in each project | |||
| 27 | specified. A summary is displayed, one line per file where there | 36 | specified. A summary is displayed, one line per file where there |
| 28 | is a difference between these three states. | 37 | is a difference between these three states. |
| 29 | 38 | ||
| 39 | The -j/--jobs option can be used to run multiple status queries | ||
| 40 | in parallel. | ||
| 41 | |||
| 30 | Status Display | 42 | Status Display |
| 31 | -------------- | 43 | -------------- |
| 32 | 44 | ||
| @@ -60,26 +72,60 @@ the following meanings: | |||
| 60 | 72 | ||
| 61 | """ | 73 | """ |
| 62 | 74 | ||
| 75 | def _Options(self, p): | ||
| 76 | p.add_option('-j', '--jobs', | ||
| 77 | dest='jobs', action='store', type='int', default=2, | ||
| 78 | help="number of projects to check simultaneously") | ||
| 79 | |||
| 80 | def _StatusHelper(self, project, clean_counter, sem, output): | ||
| 81 | """Obtains the status for a specific project. | ||
| 82 | |||
| 83 | Obtains the status for a project, redirecting the output to | ||
| 84 | the specified object. It will release the semaphore | ||
| 85 | when done. | ||
| 86 | |||
| 87 | Args: | ||
| 88 | project: Project to get status of. | ||
| 89 | clean_counter: Counter for clean projects. | ||
| 90 | sem: Semaphore, will call release() when complete. | ||
| 91 | output: Where to output the status. | ||
| 92 | """ | ||
| 93 | try: | ||
| 94 | state = project.PrintWorkTreeStatus(output) | ||
| 95 | if state == 'CLEAN': | ||
| 96 | clean_counter.next() | ||
| 97 | finally: | ||
| 98 | sem.release() | ||
| 99 | |||
| 63 | def Execute(self, opt, args): | 100 | def Execute(self, opt, args): |
| 64 | all = self.GetProjects(args) | 101 | all = self.GetProjects(args) |
| 65 | clean = 0 | 102 | counter = itertools.count() |
| 66 | 103 | ||
| 67 | on = {} | 104 | if opt.jobs == 1: |
| 68 | for project in all: | 105 | for project in all: |
| 69 | cb = project.CurrentBranch | 106 | state = project.PrintWorkTreeStatus() |
| 70 | if cb: | 107 | if state == 'CLEAN': |
| 71 | if cb not in on: | 108 | counter.next() |
| 72 | on[cb] = [] | 109 | else: |
| 73 | on[cb].append(project) | 110 | sem = _threading.Semaphore(opt.jobs) |
| 74 | 111 | threads_and_output = [] | |
| 75 | branch_names = list(on.keys()) | 112 | for project in all: |
| 76 | branch_names.sort() | 113 | sem.acquire() |
| 77 | for cb in branch_names: | 114 | |
| 78 | print '# on branch %s' % cb | 115 | class BufList(StringIO.StringIO): |
| 79 | 116 | def dump(self, ostream): | |
| 80 | for project in all: | 117 | for entry in self.buflist: |
| 81 | state = project.PrintWorkTreeStatus() | 118 | ostream.write(entry) |
| 82 | if state == 'CLEAN': | 119 | |
| 83 | clean += 1 | 120 | output = BufList() |
| 84 | if len(all) == clean: | 121 | |
| 122 | t = _threading.Thread(target=self._StatusHelper, | ||
| 123 | args=(project, counter, sem, output)) | ||
| 124 | threads_and_output.append((t, output)) | ||
| 125 | t.start() | ||
| 126 | for (t, output) in threads_and_output: | ||
| 127 | t.join() | ||
| 128 | output.dump(sys.stdout) | ||
| 129 | output.close() | ||
| 130 | if len(all) == counter.next(): | ||
| 85 | print 'nothing to commit (working directory clean)' | 131 | print 'nothing to commit (working directory clean)' |
diff --git a/subcmds/sync.py b/subcmds/sync.py index 16f1d189..bfe146b6 100644 --- a/subcmds/sync.py +++ b/subcmds/sync.py | |||
| @@ -28,6 +28,14 @@ try: | |||
| 28 | except ImportError: | 28 | except ImportError: |
| 29 | import dummy_threading as _threading | 29 | import dummy_threading as _threading |
| 30 | 30 | ||
| 31 | try: | ||
| 32 | import resource | ||
| 33 | def _rlimit_nofile(): | ||
| 34 | return resource.getrlimit(resource.RLIMIT_NOFILE) | ||
| 35 | except ImportError: | ||
| 36 | def _rlimit_nofile(): | ||
| 37 | return (256, 256) | ||
| 38 | |||
| 31 | from git_command import GIT | 39 | from git_command import GIT |
| 32 | from git_refs import R_HEADS | 40 | from git_refs import R_HEADS |
| 33 | from project import HEAD | 41 | from project import HEAD |
| @@ -39,6 +47,10 @@ from project import R_HEADS | |||
| 39 | from project import SyncBuffer | 47 | from project import SyncBuffer |
| 40 | from progress import Progress | 48 | from progress import Progress |
| 41 | 49 | ||
| 50 | class _FetchError(Exception): | ||
| 51 | """Internal error thrown in _FetchHelper() when we don't want stack trace.""" | ||
| 52 | pass | ||
| 53 | |||
| 42 | class Sync(Command, MirrorSafeCommand): | 54 | class Sync(Command, MirrorSafeCommand): |
| 43 | jobs = 1 | 55 | jobs = 1 |
| 44 | common = True | 56 | common = True |
| @@ -68,11 +80,18 @@ revision is temporarily needed. | |||
| 68 | 80 | ||
| 69 | The -s/--smart-sync option can be used to sync to a known good | 81 | The -s/--smart-sync option can be used to sync to a known good |
| 70 | build as specified by the manifest-server element in the current | 82 | build as specified by the manifest-server element in the current |
| 71 | manifest. | 83 | manifest. The -t/--smart-tag option is similar and allows you to |
| 84 | specify a custom tag/label. | ||
| 72 | 85 | ||
| 73 | The -f/--force-broken option can be used to proceed with syncing | 86 | The -f/--force-broken option can be used to proceed with syncing |
| 74 | other projects if a project sync fails. | 87 | other projects if a project sync fails. |
| 75 | 88 | ||
| 89 | The --no-clone-bundle option disables any attempt to use | ||
| 90 | $URL/clone.bundle to bootstrap a new Git repository from a | ||
| 91 | resumeable bundle file on a content delivery network. This | ||
| 92 | may be necessary if there are problems with the local Python | ||
| 93 | HTTP client or proxy configuration, but the Git binary works. | ||
| 94 | |||
| 76 | SSH Connections | 95 | SSH Connections |
| 77 | --------------- | 96 | --------------- |
| 78 | 97 | ||
| @@ -104,6 +123,8 @@ later is required to fix a server side protocol bug. | |||
| 104 | """ | 123 | """ |
| 105 | 124 | ||
| 106 | def _Options(self, p, show_smart=True): | 125 | def _Options(self, p, show_smart=True): |
| 126 | self.jobs = self.manifest.default.sync_j | ||
| 127 | |||
| 107 | p.add_option('-f', '--force-broken', | 128 | p.add_option('-f', '--force-broken', |
| 108 | dest='force_broken', action='store_true', | 129 | dest='force_broken', action='store_true', |
| 109 | help="continue sync even if a project fails to sync") | 130 | help="continue sync even if a project fails to sync") |
| @@ -116,16 +137,28 @@ later is required to fix a server side protocol bug. | |||
| 116 | p.add_option('-d','--detach', | 137 | p.add_option('-d','--detach', |
| 117 | dest='detach_head', action='store_true', | 138 | dest='detach_head', action='store_true', |
| 118 | help='detach projects back to manifest revision') | 139 | help='detach projects back to manifest revision') |
| 140 | p.add_option('-c','--current-branch', | ||
| 141 | dest='current_branch_only', action='store_true', | ||
| 142 | help='fetch only current branch from server') | ||
| 119 | p.add_option('-q','--quiet', | 143 | p.add_option('-q','--quiet', |
| 120 | dest='quiet', action='store_true', | 144 | dest='quiet', action='store_true', |
| 121 | help='be more quiet') | 145 | help='be more quiet') |
| 122 | p.add_option('-j','--jobs', | 146 | p.add_option('-j','--jobs', |
| 123 | dest='jobs', action='store', type='int', | 147 | dest='jobs', action='store', type='int', |
| 124 | help="number of projects to fetch simultaneously") | 148 | help="projects to fetch simultaneously (default %d)" % self.jobs) |
| 149 | p.add_option('-m', '--manifest-name', | ||
| 150 | dest='manifest_name', | ||
| 151 | help='temporary manifest to use for this sync', metavar='NAME.xml') | ||
| 152 | p.add_option('--no-clone-bundle', | ||
| 153 | dest='no_clone_bundle', action='store_true', | ||
| 154 | help='disable use of /clone.bundle on HTTP/HTTPS') | ||
| 125 | if show_smart: | 155 | if show_smart: |
| 126 | p.add_option('-s', '--smart-sync', | 156 | p.add_option('-s', '--smart-sync', |
| 127 | dest='smart_sync', action='store_true', | 157 | dest='smart_sync', action='store_true', |
| 128 | help='smart sync using manifest from a known good build') | 158 | help='smart sync using manifest from a known good build') |
| 159 | p.add_option('-t', '--smart-tag', | ||
| 160 | dest='smart_tag', action='store', | ||
| 161 | help='smart sync using manifest from a known tag') | ||
| 129 | 162 | ||
| 130 | g = p.add_option_group('repo Version options') | 163 | g = p.add_option_group('repo Version options') |
| 131 | g.add_option('--no-repo-verify', | 164 | g.add_option('--no-repo-verify', |
| @@ -135,20 +168,60 @@ later is required to fix a server side protocol bug. | |||
| 135 | dest='repo_upgraded', action='store_true', | 168 | dest='repo_upgraded', action='store_true', |
| 136 | help=SUPPRESS_HELP) | 169 | help=SUPPRESS_HELP) |
| 137 | 170 | ||
| 138 | def _FetchHelper(self, opt, project, lock, fetched, pm, sem): | 171 | def _FetchHelper(self, opt, project, lock, fetched, pm, sem, err_event): |
| 139 | if not project.Sync_NetworkHalf(quiet=opt.quiet): | 172 | """Main function of the fetch threads when jobs are > 1. |
| 140 | print >>sys.stderr, 'error: Cannot fetch %s' % project.name | 173 | |
| 141 | if opt.force_broken: | 174 | Args: |
| 142 | print >>sys.stderr, 'warn: --force-broken, continuing to sync' | 175 | opt: Program options returned from optparse. See _Options(). |
| 143 | else: | 176 | project: Project object for the project to fetch. |
| 144 | sem.release() | 177 | lock: Lock for accessing objects that are shared amongst multiple |
| 145 | sys.exit(1) | 178 | _FetchHelper() threads. |
| 179 | fetched: set object that we will add project.gitdir to when we're done | ||
| 180 | (with our lock held). | ||
| 181 | pm: Instance of a Project object. We will call pm.update() (with our | ||
| 182 | lock held). | ||
| 183 | sem: We'll release() this semaphore when we exit so that another thread | ||
| 184 | can be started up. | ||
| 185 | err_event: We'll set this event in the case of an error (after printing | ||
| 186 | out info about the error). | ||
| 187 | """ | ||
| 188 | # We'll set to true once we've locked the lock. | ||
| 189 | did_lock = False | ||
| 190 | |||
| 191 | # Encapsulate everything in a try/except/finally so that: | ||
| 192 | # - We always set err_event in the case of an exception. | ||
| 193 | # - We always make sure we call sem.release(). | ||
| 194 | # - We always make sure we unlock the lock if we locked it. | ||
| 195 | try: | ||
| 196 | try: | ||
| 197 | success = project.Sync_NetworkHalf( | ||
| 198 | quiet=opt.quiet, | ||
| 199 | current_branch_only=opt.current_branch_only, | ||
| 200 | clone_bundle=not opt.no_clone_bundle) | ||
| 201 | |||
| 202 | # Lock around all the rest of the code, since printing, updating a set | ||
| 203 | # and Progress.update() are not thread safe. | ||
| 204 | lock.acquire() | ||
| 205 | did_lock = True | ||
| 206 | |||
| 207 | if not success: | ||
| 208 | print >>sys.stderr, 'error: Cannot fetch %s' % project.name | ||
| 209 | if opt.force_broken: | ||
| 210 | print >>sys.stderr, 'warn: --force-broken, continuing to sync' | ||
| 211 | else: | ||
| 212 | raise _FetchError() | ||
| 146 | 213 | ||
| 147 | lock.acquire() | 214 | fetched.add(project.gitdir) |
| 148 | fetched.add(project.gitdir) | 215 | pm.update() |
| 149 | pm.update() | 216 | except _FetchError: |
| 150 | lock.release() | 217 | err_event.set() |
| 151 | sem.release() | 218 | except: |
| 219 | err_event.set() | ||
| 220 | raise | ||
| 221 | finally: | ||
| 222 | if did_lock: | ||
| 223 | lock.release() | ||
| 224 | sem.release() | ||
| 152 | 225 | ||
| 153 | def _Fetch(self, projects, opt): | 226 | def _Fetch(self, projects, opt): |
| 154 | fetched = set() | 227 | fetched = set() |
| @@ -157,7 +230,10 @@ later is required to fix a server side protocol bug. | |||
| 157 | if self.jobs == 1: | 230 | if self.jobs == 1: |
| 158 | for project in projects: | 231 | for project in projects: |
| 159 | pm.update() | 232 | pm.update() |
| 160 | if project.Sync_NetworkHalf(quiet=opt.quiet): | 233 | if project.Sync_NetworkHalf( |
| 234 | quiet=opt.quiet, | ||
| 235 | current_branch_only=opt.current_branch_only, | ||
| 236 | clone_bundle=not opt.no_clone_bundle): | ||
| 161 | fetched.add(project.gitdir) | 237 | fetched.add(project.gitdir) |
| 162 | else: | 238 | else: |
| 163 | print >>sys.stderr, 'error: Cannot fetch %s' % project.name | 239 | print >>sys.stderr, 'error: Cannot fetch %s' % project.name |
| @@ -169,7 +245,13 @@ later is required to fix a server side protocol bug. | |||
| 169 | threads = set() | 245 | threads = set() |
| 170 | lock = _threading.Lock() | 246 | lock = _threading.Lock() |
| 171 | sem = _threading.Semaphore(self.jobs) | 247 | sem = _threading.Semaphore(self.jobs) |
| 248 | err_event = _threading.Event() | ||
| 172 | for project in projects: | 249 | for project in projects: |
| 250 | # Check for any errors before starting any new threads. | ||
| 251 | # ...we'll let existing threads finish, though. | ||
| 252 | if err_event.isSet(): | ||
| 253 | break | ||
| 254 | |||
| 173 | sem.acquire() | 255 | sem.acquire() |
| 174 | t = _threading.Thread(target = self._FetchHelper, | 256 | t = _threading.Thread(target = self._FetchHelper, |
| 175 | args = (opt, | 257 | args = (opt, |
| @@ -177,13 +259,19 @@ later is required to fix a server side protocol bug. | |||
| 177 | lock, | 259 | lock, |
| 178 | fetched, | 260 | fetched, |
| 179 | pm, | 261 | pm, |
| 180 | sem)) | 262 | sem, |
| 263 | err_event)) | ||
| 181 | threads.add(t) | 264 | threads.add(t) |
| 182 | t.start() | 265 | t.start() |
| 183 | 266 | ||
| 184 | for t in threads: | 267 | for t in threads: |
| 185 | t.join() | 268 | t.join() |
| 186 | 269 | ||
| 270 | # If we saw an error, exit with code 1 so that other scripts can check. | ||
| 271 | if err_event.isSet(): | ||
| 272 | print >>sys.stderr, '\nerror: Exited sync due to fetch errors' | ||
| 273 | sys.exit(1) | ||
| 274 | |||
| 187 | pm.end() | 275 | pm.end() |
| 188 | for project in projects: | 276 | for project in projects: |
| 189 | project.bare_git.gc('--auto') | 277 | project.bare_git.gc('--auto') |
| @@ -191,7 +279,7 @@ later is required to fix a server side protocol bug. | |||
| 191 | 279 | ||
| 192 | def UpdateProjectList(self): | 280 | def UpdateProjectList(self): |
| 193 | new_project_paths = [] | 281 | new_project_paths = [] |
| 194 | for project in self.manifest.projects.values(): | 282 | for project in self.GetProjects(None, missing_ok=True): |
| 195 | if project.relpath: | 283 | if project.relpath: |
| 196 | new_project_paths.append(project.relpath) | 284 | new_project_paths.append(project.relpath) |
| 197 | file_name = 'project.list' | 285 | file_name = 'project.list' |
| @@ -220,7 +308,8 @@ later is required to fix a server side protocol bug. | |||
| 220 | worktree = os.path.join(self.manifest.topdir, path), | 308 | worktree = os.path.join(self.manifest.topdir, path), |
| 221 | relpath = path, | 309 | relpath = path, |
| 222 | revisionExpr = 'HEAD', | 310 | revisionExpr = 'HEAD', |
| 223 | revisionId = None) | 311 | revisionId = None, |
| 312 | groups = None) | ||
| 224 | 313 | ||
| 225 | if project.IsDirty(): | 314 | if project.IsDirty(): |
| 226 | print >>sys.stderr, 'error: Cannot remove project "%s": \ | 315 | print >>sys.stderr, 'error: Cannot remove project "%s": \ |
| @@ -251,34 +340,51 @@ uncommitted changes are present' % project.relpath | |||
| 251 | def Execute(self, opt, args): | 340 | def Execute(self, opt, args): |
| 252 | if opt.jobs: | 341 | if opt.jobs: |
| 253 | self.jobs = opt.jobs | 342 | self.jobs = opt.jobs |
| 343 | if self.jobs > 1: | ||
| 344 | soft_limit, _ = _rlimit_nofile() | ||
| 345 | self.jobs = min(self.jobs, (soft_limit - 5) / 3) | ||
| 346 | |||
| 254 | if opt.network_only and opt.detach_head: | 347 | if opt.network_only and opt.detach_head: |
| 255 | print >>sys.stderr, 'error: cannot combine -n and -d' | 348 | print >>sys.stderr, 'error: cannot combine -n and -d' |
| 256 | sys.exit(1) | 349 | sys.exit(1) |
| 257 | if opt.network_only and opt.local_only: | 350 | if opt.network_only and opt.local_only: |
| 258 | print >>sys.stderr, 'error: cannot combine -n and -l' | 351 | print >>sys.stderr, 'error: cannot combine -n and -l' |
| 259 | sys.exit(1) | 352 | sys.exit(1) |
| 353 | if opt.manifest_name and opt.smart_sync: | ||
| 354 | print >>sys.stderr, 'error: cannot combine -m and -s' | ||
| 355 | sys.exit(1) | ||
| 356 | if opt.manifest_name and opt.smart_tag: | ||
| 357 | print >>sys.stderr, 'error: cannot combine -m and -t' | ||
| 358 | sys.exit(1) | ||
| 260 | 359 | ||
| 261 | if opt.smart_sync: | 360 | if opt.manifest_name: |
| 361 | self.manifest.Override(opt.manifest_name) | ||
| 362 | |||
| 363 | if opt.smart_sync or opt.smart_tag: | ||
| 262 | if not self.manifest.manifest_server: | 364 | if not self.manifest.manifest_server: |
| 263 | print >>sys.stderr, \ | 365 | print >>sys.stderr, \ |
| 264 | 'error: cannot smart sync: no manifest server defined in manifest' | 366 | 'error: cannot smart sync: no manifest server defined in manifest' |
| 265 | sys.exit(1) | 367 | sys.exit(1) |
| 266 | try: | 368 | try: |
| 267 | server = xmlrpclib.Server(self.manifest.manifest_server) | 369 | server = xmlrpclib.Server(self.manifest.manifest_server) |
| 268 | p = self.manifest.manifestProject | 370 | if opt.smart_sync: |
| 269 | b = p.GetBranch(p.CurrentBranch) | 371 | p = self.manifest.manifestProject |
| 270 | branch = b.merge | 372 | b = p.GetBranch(p.CurrentBranch) |
| 271 | if branch.startswith(R_HEADS): | 373 | branch = b.merge |
| 272 | branch = branch[len(R_HEADS):] | 374 | if branch.startswith(R_HEADS): |
| 273 | 375 | branch = branch[len(R_HEADS):] | |
| 274 | env = os.environ.copy() | 376 | |
| 275 | if (env.has_key('TARGET_PRODUCT') and | 377 | env = os.environ.copy() |
| 276 | env.has_key('TARGET_BUILD_VARIANT')): | 378 | if (env.has_key('TARGET_PRODUCT') and |
| 277 | target = '%s-%s' % (env['TARGET_PRODUCT'], | 379 | env.has_key('TARGET_BUILD_VARIANT')): |
| 278 | env['TARGET_BUILD_VARIANT']) | 380 | target = '%s-%s' % (env['TARGET_PRODUCT'], |
| 279 | [success, manifest_str] = server.GetApprovedManifest(branch, target) | 381 | env['TARGET_BUILD_VARIANT']) |
| 382 | [success, manifest_str] = server.GetApprovedManifest(branch, target) | ||
| 383 | else: | ||
| 384 | [success, manifest_str] = server.GetApprovedManifest(branch) | ||
| 280 | else: | 385 | else: |
| 281 | [success, manifest_str] = server.GetApprovedManifest(branch) | 386 | assert(opt.smart_tag) |
| 387 | [success, manifest_str] = server.GetManifest(opt.smart_tag) | ||
| 282 | 388 | ||
| 283 | if success: | 389 | if success: |
| 284 | manifest_name = "smart_sync_override.xml" | 390 | manifest_name = "smart_sync_override.xml" |
| @@ -313,7 +419,8 @@ uncommitted changes are present' % project.relpath | |||
| 313 | _PostRepoUpgrade(self.manifest) | 419 | _PostRepoUpgrade(self.manifest) |
| 314 | 420 | ||
| 315 | if not opt.local_only: | 421 | if not opt.local_only: |
| 316 | mp.Sync_NetworkHalf(quiet=opt.quiet) | 422 | mp.Sync_NetworkHalf(quiet=opt.quiet, |
| 423 | current_branch_only=opt.current_branch_only) | ||
| 317 | 424 | ||
| 318 | if mp.HasChanges: | 425 | if mp.HasChanges: |
| 319 | syncbuf = SyncBuffer(mp.config) | 426 | syncbuf = SyncBuffer(mp.config) |
| @@ -321,6 +428,8 @@ uncommitted changes are present' % project.relpath | |||
| 321 | if not syncbuf.Finish(): | 428 | if not syncbuf.Finish(): |
| 322 | sys.exit(1) | 429 | sys.exit(1) |
| 323 | self.manifest._Unload() | 430 | self.manifest._Unload() |
| 431 | if opt.jobs is None: | ||
| 432 | self.jobs = self.manifest.default.sync_j | ||
| 324 | all = self.GetProjects(args, missing_ok=True) | 433 | all = self.GetProjects(args, missing_ok=True) |
| 325 | 434 | ||
| 326 | if not opt.local_only: | 435 | if not opt.local_only: |
| @@ -336,14 +445,7 @@ uncommitted changes are present' % project.relpath | |||
| 336 | # bail out now; the rest touches the working tree | 445 | # bail out now; the rest touches the working tree |
| 337 | return | 446 | return |
| 338 | 447 | ||
| 339 | if mp.HasChanges: | 448 | self.manifest._Unload() |
| 340 | syncbuf = SyncBuffer(mp.config) | ||
| 341 | mp.Sync_LocalHalf(syncbuf) | ||
| 342 | if not syncbuf.Finish(): | ||
| 343 | sys.exit(1) | ||
| 344 | _ReloadManifest(self) | ||
| 345 | mp = self.manifest.manifestProject | ||
| 346 | |||
| 347 | all = self.GetProjects(args, missing_ok=True) | 449 | all = self.GetProjects(args, missing_ok=True) |
| 348 | missing = [] | 450 | missing = [] |
| 349 | for project in all: | 451 | for project in all: |
| @@ -370,16 +472,10 @@ uncommitted changes are present' % project.relpath | |||
| 370 | if not syncbuf.Finish(): | 472 | if not syncbuf.Finish(): |
| 371 | sys.exit(1) | 473 | sys.exit(1) |
| 372 | 474 | ||
| 373 | def _ReloadManifest(cmd): | 475 | # If there's a notice that's supposed to print at the end of the sync, print |
| 374 | old = cmd.manifest | 476 | # it now... |
| 375 | new = cmd.GetManifest(reparse=True) | 477 | if self.manifest.notice: |
| 376 | 478 | print self.manifest.notice | |
| 377 | if old.__class__ != new.__class__: | ||
| 378 | print >>sys.stderr, 'NOTICE: manifest format has changed ***' | ||
| 379 | new.Upgrade_Local(old) | ||
| 380 | else: | ||
| 381 | if new.notice: | ||
| 382 | print new.notice | ||
| 383 | 479 | ||
| 384 | def _PostRepoUpgrade(manifest): | 480 | def _PostRepoUpgrade(manifest): |
| 385 | for project in manifest.projects.values(): | 481 | for project in manifest.projects.values(): |
diff --git a/subcmds/upload.py b/subcmds/upload.py index 20822096..c9312973 100644 --- a/subcmds/upload.py +++ b/subcmds/upload.py | |||
| @@ -19,7 +19,8 @@ import sys | |||
| 19 | 19 | ||
| 20 | from command import InteractiveCommand | 20 | from command import InteractiveCommand |
| 21 | from editor import Editor | 21 | from editor import Editor |
| 22 | from error import UploadError | 22 | from error import HookError, UploadError |
| 23 | from project import RepoHook | ||
| 23 | 24 | ||
| 24 | UNUSUAL_COMMIT_THRESHOLD = 5 | 25 | UNUSUAL_COMMIT_THRESHOLD = 5 |
| 25 | 26 | ||
| @@ -72,7 +73,7 @@ Configuration | |||
| 72 | 73 | ||
| 73 | review.URL.autoupload: | 74 | review.URL.autoupload: |
| 74 | 75 | ||
| 75 | To disable the "Upload ... (y/n)?" prompt, you can set a per-project | 76 | To disable the "Upload ... (y/N)?" prompt, you can set a per-project |
| 76 | or global Git configuration option. If review.URL.autoupload is set | 77 | or global Git configuration option. If review.URL.autoupload is set |
| 77 | to "true" then repo will assume you always answer "y" at the prompt, | 78 | to "true" then repo will assume you always answer "y" at the prompt, |
| 78 | and will not prompt you further. If it is set to "false" then repo | 79 | and will not prompt you further. If it is set to "false" then repo |
| @@ -102,6 +103,14 @@ or in the .git/config within the project. For example: | |||
| 102 | autoupload = true | 103 | autoupload = true |
| 103 | autocopy = johndoe@company.com,my-team-alias@company.com | 104 | autocopy = johndoe@company.com,my-team-alias@company.com |
| 104 | 105 | ||
| 106 | review.URL.uploadtopic: | ||
| 107 | |||
| 108 | To add a topic branch whenever uploading a commit, you can set a | ||
| 109 | per-project or global Git option to do so. If review.URL.uploadtopic | ||
| 110 | is set to "true" then repo will assume you always want the equivalent | ||
| 111 | of the -t option to the repo command. If unset or set to "false" then | ||
| 112 | repo will make use of only the command line option. | ||
| 113 | |||
| 105 | References | 114 | References |
| 106 | ---------- | 115 | ---------- |
| 107 | 116 | ||
| @@ -119,6 +128,38 @@ Gerrit Code Review: http://code.google.com/p/gerrit/ | |||
| 119 | p.add_option('--cc', | 128 | p.add_option('--cc', |
| 120 | type='string', action='append', dest='cc', | 129 | type='string', action='append', dest='cc', |
| 121 | help='Also send email to these email addresses.') | 130 | help='Also send email to these email addresses.') |
| 131 | p.add_option('--br', | ||
| 132 | type='string', action='store', dest='branch', | ||
| 133 | help='Branch to upload.') | ||
| 134 | p.add_option('--cbr', '--current-branch', | ||
| 135 | dest='current_branch', action='store_true', | ||
| 136 | help='Upload current git branch.') | ||
| 137 | p.add_option('-d', '--draft', | ||
| 138 | action='store_true', dest='draft', default=False, | ||
| 139 | help='If specified, upload as a draft.') | ||
| 140 | |||
| 141 | # Options relating to upload hook. Note that verify and no-verify are NOT | ||
| 142 | # opposites of each other, which is why they store to different locations. | ||
| 143 | # We are using them to match 'git commit' syntax. | ||
| 144 | # | ||
| 145 | # Combinations: | ||
| 146 | # - no-verify=False, verify=False (DEFAULT): | ||
| 147 | # If stdout is a tty, can prompt about running upload hooks if needed. | ||
| 148 | # If user denies running hooks, the upload is cancelled. If stdout is | ||
| 149 | # not a tty and we would need to prompt about upload hooks, upload is | ||
| 150 | # cancelled. | ||
| 151 | # - no-verify=False, verify=True: | ||
| 152 | # Always run upload hooks with no prompt. | ||
| 153 | # - no-verify=True, verify=False: | ||
| 154 | # Never run upload hooks, but upload anyway (AKA bypass hooks). | ||
| 155 | # - no-verify=True, verify=True: | ||
| 156 | # Invalid | ||
| 157 | p.add_option('--no-verify', | ||
| 158 | dest='bypass_hooks', action='store_true', | ||
| 159 | help='Do not run the upload hook.') | ||
| 160 | p.add_option('--verify', | ||
| 161 | dest='allow_all_hooks', action='store_true', | ||
| 162 | help='Run the upload hook without prompting.') | ||
| 122 | 163 | ||
| 123 | def _SingleBranch(self, opt, branch, people): | 164 | def _SingleBranch(self, opt, branch, people): |
| 124 | project = branch.project | 165 | project = branch.project |
| @@ -135,7 +176,7 @@ Gerrit Code Review: http://code.google.com/p/gerrit/ | |||
| 135 | date = branch.date | 176 | date = branch.date |
| 136 | list = branch.commits | 177 | list = branch.commits |
| 137 | 178 | ||
| 138 | print 'Upload project %s/:' % project.relpath | 179 | print 'Upload project %s/ to remote branch %s:' % (project.relpath, project.revisionExpr) |
| 139 | print ' branch %s (%2d commit%s, %s):' % ( | 180 | print ' branch %s (%2d commit%s, %s):' % ( |
| 140 | name, | 181 | name, |
| 141 | len(list), | 182 | len(list), |
| @@ -144,7 +185,7 @@ Gerrit Code Review: http://code.google.com/p/gerrit/ | |||
| 144 | for commit in list: | 185 | for commit in list: |
| 145 | print ' %s' % commit | 186 | print ' %s' % commit |
| 146 | 187 | ||
| 147 | sys.stdout.write('to %s (y/n)? ' % remote.review) | 188 | sys.stdout.write('to %s (y/N)? ' % remote.review) |
| 148 | answer = sys.stdin.readline().strip() | 189 | answer = sys.stdin.readline().strip() |
| 149 | answer = answer in ('y', 'Y', 'yes', '1', 'true', 't') | 190 | answer = answer in ('y', 'Y', 'yes', '1', 'true', 't') |
| 150 | 191 | ||
| @@ -175,11 +216,12 @@ Gerrit Code Review: http://code.google.com/p/gerrit/ | |||
| 175 | 216 | ||
| 176 | if b: | 217 | if b: |
| 177 | script.append('#') | 218 | script.append('#') |
| 178 | script.append('# branch %s (%2d commit%s, %s):' % ( | 219 | script.append('# branch %s (%2d commit%s, %s) to remote branch %s:' % ( |
| 179 | name, | 220 | name, |
| 180 | len(list), | 221 | len(list), |
| 181 | len(list) != 1 and 's' or '', | 222 | len(list) != 1 and 's' or '', |
| 182 | date)) | 223 | date, |
| 224 | project.revisionExpr)) | ||
| 183 | for commit in list: | 225 | for commit in list: |
| 184 | script.append('# %s' % commit) | 226 | script.append('# %s' % commit) |
| 185 | b[name] = branch | 227 | b[name] = branch |
| @@ -188,6 +230,11 @@ Gerrit Code Review: http://code.google.com/p/gerrit/ | |||
| 188 | branches[project.name] = b | 230 | branches[project.name] = b |
| 189 | script.append('') | 231 | script.append('') |
| 190 | 232 | ||
| 233 | script = [ x.encode('utf-8') | ||
| 234 | if issubclass(type(x), unicode) | ||
| 235 | else x | ||
| 236 | for x in script ] | ||
| 237 | |||
| 191 | script = Editor.EditString("\n".join(script)).split("\n") | 238 | script = Editor.EditString("\n".join(script)).split("\n") |
| 192 | 239 | ||
| 193 | project_re = re.compile(r'^#?\s*project\s*([^\s]+)/:$') | 240 | project_re = re.compile(r'^#?\s*project\s*([^\s]+)/:$') |
| @@ -267,7 +314,7 @@ Gerrit Code Review: http://code.google.com/p/gerrit/ | |||
| 267 | 314 | ||
| 268 | # if they want to auto upload, let's not ask because it could be automated | 315 | # if they want to auto upload, let's not ask because it could be automated |
| 269 | if answer is None: | 316 | if answer is None: |
| 270 | sys.stdout.write('Uncommitted changes in ' + branch.project.name + ' (did you forget to amend?). Continue uploading? (y/n) ') | 317 | sys.stdout.write('Uncommitted changes in ' + branch.project.name + ' (did you forget to amend?). Continue uploading? (y/N) ') |
| 271 | a = sys.stdin.readline().strip().lower() | 318 | a = sys.stdin.readline().strip().lower() |
| 272 | if a not in ('y', 'yes', 't', 'true', 'on'): | 319 | if a not in ('y', 'yes', 't', 'true', 'on'): |
| 273 | print >>sys.stderr, "skipping upload" | 320 | print >>sys.stderr, "skipping upload" |
| @@ -275,7 +322,12 @@ Gerrit Code Review: http://code.google.com/p/gerrit/ | |||
| 275 | branch.error = 'User aborted' | 322 | branch.error = 'User aborted' |
| 276 | continue | 323 | continue |
| 277 | 324 | ||
| 278 | branch.UploadForReview(people, auto_topic=opt.auto_topic) | 325 | # Check if topic branches should be sent to the server during upload |
| 326 | if opt.auto_topic is not True: | ||
| 327 | key = 'review.%s.uploadtopic' % branch.project.remote.review | ||
| 328 | opt.auto_topic = branch.project.config.GetBoolean(key) | ||
| 329 | |||
| 330 | branch.UploadForReview(people, auto_topic=opt.auto_topic, draft=opt.draft) | ||
| 279 | branch.uploaded = True | 331 | branch.uploaded = True |
| 280 | except UploadError, e: | 332 | except UploadError, e: |
| 281 | branch.error = e | 333 | branch.error = e |
| @@ -312,6 +364,29 @@ Gerrit Code Review: http://code.google.com/p/gerrit/ | |||
| 312 | pending = [] | 364 | pending = [] |
| 313 | reviewers = [] | 365 | reviewers = [] |
| 314 | cc = [] | 366 | cc = [] |
| 367 | branch = None | ||
| 368 | |||
| 369 | if opt.branch: | ||
| 370 | branch = opt.branch | ||
| 371 | |||
| 372 | for project in project_list: | ||
| 373 | if opt.current_branch: | ||
| 374 | cbr = project.CurrentBranch | ||
| 375 | avail = [project.GetUploadableBranch(cbr)] if cbr else None | ||
| 376 | else: | ||
| 377 | avail = project.GetUploadableBranches(branch) | ||
| 378 | if avail: | ||
| 379 | pending.append((project, avail)) | ||
| 380 | |||
| 381 | if pending and (not opt.bypass_hooks): | ||
| 382 | hook = RepoHook('pre-upload', self.manifest.repo_hooks_project, | ||
| 383 | self.manifest.topdir, abort_if_user_denies=True) | ||
| 384 | pending_proj_names = [project.name for (project, avail) in pending] | ||
| 385 | try: | ||
| 386 | hook.Run(opt.allow_all_hooks, project_list=pending_proj_names) | ||
| 387 | except HookError, e: | ||
| 388 | print >>sys.stderr, "ERROR: %s" % str(e) | ||
| 389 | return | ||
| 315 | 390 | ||
| 316 | if opt.reviewers: | 391 | if opt.reviewers: |
| 317 | reviewers = _SplitEmails(opt.reviewers) | 392 | reviewers = _SplitEmails(opt.reviewers) |
| @@ -319,11 +394,6 @@ Gerrit Code Review: http://code.google.com/p/gerrit/ | |||
| 319 | cc = _SplitEmails(opt.cc) | 394 | cc = _SplitEmails(opt.cc) |
| 320 | people = (reviewers,cc) | 395 | people = (reviewers,cc) |
| 321 | 396 | ||
| 322 | for project in project_list: | ||
| 323 | avail = project.GetUploadableBranches() | ||
| 324 | if avail: | ||
| 325 | pending.append((project, avail)) | ||
| 326 | |||
| 327 | if not pending: | 397 | if not pending: |
| 328 | print >>sys.stdout, "no branches ready for upload" | 398 | print >>sys.stdout, "no branches ready for upload" |
| 329 | elif len(pending) == 1 and len(pending[0][1]) == 1: | 399 | elif len(pending) == 1 and len(pending[0][1]) == 1: |
diff --git a/subcmds/version.py b/subcmds/version.py index 83e77d0b..03195f88 100644 --- a/subcmds/version.py +++ b/subcmds/version.py | |||
| @@ -19,6 +19,9 @@ from git_command import git | |||
| 19 | from project import HEAD | 19 | from project import HEAD |
| 20 | 20 | ||
| 21 | class Version(Command, MirrorSafeCommand): | 21 | class Version(Command, MirrorSafeCommand): |
| 22 | wrapper_version = None | ||
| 23 | wrapper_path = None | ||
| 24 | |||
| 22 | common = False | 25 | common = False |
| 23 | helpSummary = "Display the version of repo" | 26 | helpSummary = "Display the version of repo" |
| 24 | helpUsage = """ | 27 | helpUsage = """ |
| @@ -31,5 +34,10 @@ class Version(Command, MirrorSafeCommand): | |||
| 31 | 34 | ||
| 32 | print 'repo version %s' % rp.work_git.describe(HEAD) | 35 | print 'repo version %s' % rp.work_git.describe(HEAD) |
| 33 | print ' (from %s)' % rem.url | 36 | print ' (from %s)' % rem.url |
| 37 | |||
| 38 | if Version.wrapper_path is not None: | ||
| 39 | print 'repo launcher version %s' % Version.wrapper_version | ||
| 40 | print ' (from %s)' % Version.wrapper_path | ||
| 41 | |||
| 34 | print git.version().strip() | 42 | print git.version().strip() |
| 35 | print 'Python %s' % sys.version | 43 | print 'Python %s' % sys.version |
