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 |