diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2008-10-21 07:00:00 -0700 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2008-10-21 07:00:00 -0700 |
commit | cf31fe9b4fb650b27e19f5d7ee7297e383660caf (patch) | |
tree | d04ca6a45d579dca5e5469606c48c405aee68f4b /subcmds | |
download | git-repo-cf31fe9b4fb650b27e19f5d7ee7297e383660caf.tar.gz |
Initial Contributionv1.0
Diffstat (limited to 'subcmds')
-rw-r--r-- | subcmds/__init__.py | 49 | ||||
-rw-r--r-- | subcmds/compute_snapshot_check.py | 169 | ||||
-rw-r--r-- | subcmds/diff.py | 27 | ||||
-rw-r--r-- | subcmds/forall.py | 82 | ||||
-rw-r--r-- | subcmds/help.py | 147 | ||||
-rw-r--r-- | subcmds/init.py | 193 | ||||
-rw-r--r-- | subcmds/prune.py | 59 | ||||
-rw-r--r-- | subcmds/stage.py | 108 | ||||
-rw-r--r-- | subcmds/start.py | 51 | ||||
-rw-r--r-- | subcmds/status.py | 27 | ||||
-rw-r--r-- | subcmds/sync.py | 150 | ||||
-rw-r--r-- | subcmds/upload.py | 180 |
12 files changed, 1242 insertions, 0 deletions
diff --git a/subcmds/__init__.py b/subcmds/__init__.py new file mode 100644 index 00000000..a2286e78 --- /dev/null +++ b/subcmds/__init__.py | |||
@@ -0,0 +1,49 @@ | |||
1 | # | ||
2 | # Copyright (C) 2008 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 os | ||
17 | |||
18 | all = {} | ||
19 | |||
20 | my_dir = os.path.dirname(__file__) | ||
21 | for py in os.listdir(my_dir): | ||
22 | if py == '__init__.py': | ||
23 | continue | ||
24 | |||
25 | if py.endswith('.py'): | ||
26 | name = py[:-3] | ||
27 | |||
28 | clsn = name.capitalize() | ||
29 | while clsn.find('_') > 0: | ||
30 | h = clsn.index('_') | ||
31 | clsn = clsn[0:h] + clsn[h + 1:].capitalize() | ||
32 | |||
33 | mod = __import__(__name__, | ||
34 | globals(), | ||
35 | locals(), | ||
36 | ['%s' % name]) | ||
37 | mod = getattr(mod, name) | ||
38 | try: | ||
39 | cmd = getattr(mod, clsn)() | ||
40 | except AttributeError: | ||
41 | raise SyntaxError, '%s/%s does not define class %s' % ( | ||
42 | __name__, py, clsn) | ||
43 | |||
44 | name = name.replace('_', '-') | ||
45 | cmd.NAME = name | ||
46 | all[name] = cmd | ||
47 | |||
48 | if 'help' in all: | ||
49 | all['help'].commands = all | ||
diff --git a/subcmds/compute_snapshot_check.py b/subcmds/compute_snapshot_check.py new file mode 100644 index 00000000..82db359a --- /dev/null +++ b/subcmds/compute_snapshot_check.py | |||
@@ -0,0 +1,169 @@ | |||
1 | # | ||
2 | # Copyright (C) 2008 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 os | ||
17 | import sys | ||
18 | import tempfile | ||
19 | |||
20 | from command import Command | ||
21 | from error import GitError, NoSuchProjectError | ||
22 | from git_config import IsId | ||
23 | from import_tar import ImportTar | ||
24 | from import_zip import ImportZip | ||
25 | from project import Project | ||
26 | from remote import Remote | ||
27 | |||
28 | def _ToCommit(project, rev): | ||
29 | return project.bare_git.rev_parse('--verify', '%s^0' % rev) | ||
30 | |||
31 | def _Missing(project, rev): | ||
32 | return project._revlist('--objects', rev, '--not', '--all') | ||
33 | |||
34 | |||
35 | class ComputeSnapshotCheck(Command): | ||
36 | common = False | ||
37 | helpSummary = "Compute the check value for a new snapshot" | ||
38 | helpUsage = """ | ||
39 | %prog -p NAME -v VERSION -s FILE [options] | ||
40 | """ | ||
41 | helpDescription = """ | ||
42 | %prog computes and then displays the proper check value for a | ||
43 | snapshot, so it can be pasted into the manifest file for a project. | ||
44 | """ | ||
45 | |||
46 | def _Options(self, p): | ||
47 | g = p.add_option_group('Snapshot description options') | ||
48 | g.add_option('-p', '--project', | ||
49 | dest='project', metavar='NAME', | ||
50 | help='destination project name') | ||
51 | g.add_option('-v', '--version', | ||
52 | dest='version', metavar='VERSION', | ||
53 | help='upstream version/revision identifier') | ||
54 | g.add_option('-s', '--snapshot', | ||
55 | dest='snapshot', metavar='PATH', | ||
56 | help='local tarball path') | ||
57 | g.add_option('--new-project', | ||
58 | dest='new_project', action='store_true', | ||
59 | help='destinition is a new project') | ||
60 | g.add_option('--keep', | ||
61 | dest='keep_git', action='store_true', | ||
62 | help='keep the temporary git repository') | ||
63 | |||
64 | g = p.add_option_group('Base revision grafting options') | ||
65 | g.add_option('--prior', | ||
66 | dest='prior', metavar='COMMIT', | ||
67 | help='prior revision checksum') | ||
68 | |||
69 | g = p.add_option_group('Path mangling options') | ||
70 | g.add_option('--strip-prefix', | ||
71 | dest='strip_prefix', metavar='PREFIX', | ||
72 | help='remove prefix from all paths on import') | ||
73 | g.add_option('--insert-prefix', | ||
74 | dest='insert_prefix', metavar='PREFIX', | ||
75 | help='insert prefix before all paths on import') | ||
76 | |||
77 | |||
78 | def _Compute(self, opt): | ||
79 | try: | ||
80 | real_project = self.GetProjects([opt.project])[0] | ||
81 | except NoSuchProjectError: | ||
82 | if opt.new_project: | ||
83 | print >>sys.stderr, \ | ||
84 | "warning: project '%s' does not exist" % opt.project | ||
85 | else: | ||
86 | raise NoSuchProjectError(opt.project) | ||
87 | |||
88 | self._tmpdir = tempfile.mkdtemp() | ||
89 | project = Project(manifest = self.manifest, | ||
90 | name = opt.project, | ||
91 | remote = Remote('origin'), | ||
92 | gitdir = os.path.join(self._tmpdir, '.git'), | ||
93 | worktree = self._tmpdir, | ||
94 | relpath = opt.project, | ||
95 | revision = 'refs/heads/master') | ||
96 | project._InitGitDir() | ||
97 | |||
98 | url = 'file://%s' % os.path.abspath(opt.snapshot) | ||
99 | |||
100 | imp = None | ||
101 | for cls in [ImportTar, ImportZip]: | ||
102 | if cls.CanAccept(url): | ||
103 | imp = cls() | ||
104 | break | ||
105 | if not imp: | ||
106 | print >>sys.stderr, 'error: %s unsupported' % opt.snapshot | ||
107 | sys.exit(1) | ||
108 | |||
109 | imp.SetProject(project) | ||
110 | imp.SetVersion(opt.version) | ||
111 | imp.AddUrl(url) | ||
112 | |||
113 | if opt.prior: | ||
114 | if opt.new_project: | ||
115 | if not IsId(opt.prior): | ||
116 | print >>sys.stderr, 'error: --prior=%s not valid' % opt.prior | ||
117 | sys.exit(1) | ||
118 | else: | ||
119 | try: | ||
120 | opt.prior = _ToCommit(real_project, opt.prior) | ||
121 | missing = _Missing(real_project, opt.prior) | ||
122 | except GitError, e: | ||
123 | print >>sys.stderr,\ | ||
124 | 'error: --prior=%s not valid\n%s' \ | ||
125 | % (opt.prior, e) | ||
126 | sys.exit(1) | ||
127 | if missing: | ||
128 | print >>sys.stderr,\ | ||
129 | 'error: --prior=%s is valid, but is not reachable' \ | ||
130 | % opt.prior | ||
131 | sys.exit(1) | ||
132 | imp.SetParent(opt.prior) | ||
133 | |||
134 | src = opt.strip_prefix | ||
135 | dst = opt.insert_prefix | ||
136 | if src or dst: | ||
137 | if src is None: | ||
138 | src = '' | ||
139 | if dst is None: | ||
140 | dst = '' | ||
141 | imp.RemapPath(src, dst) | ||
142 | commitId = imp.Import() | ||
143 | |||
144 | print >>sys.stderr,"%s\t%s" % (commitId, imp.version) | ||
145 | return project | ||
146 | |||
147 | def Execute(self, opt, args): | ||
148 | if args \ | ||
149 | or not opt.project \ | ||
150 | or not opt.version \ | ||
151 | or not opt.snapshot: | ||
152 | self.Usage() | ||
153 | |||
154 | success = False | ||
155 | project = None | ||
156 | try: | ||
157 | self._tmpdir = None | ||
158 | project = self._Compute(opt) | ||
159 | finally: | ||
160 | if project and opt.keep_git: | ||
161 | print 'GIT_DIR = %s' % (project.gitdir) | ||
162 | elif self._tmpdir: | ||
163 | for root, dirs, files in os.walk(self._tmpdir, topdown=False): | ||
164 | for name in files: | ||
165 | os.remove(os.path.join(root, name)) | ||
166 | for name in dirs: | ||
167 | os.rmdir(os.path.join(root, name)) | ||
168 | os.rmdir(self._tmpdir) | ||
169 | |||
diff --git a/subcmds/diff.py b/subcmds/diff.py new file mode 100644 index 00000000..e0247140 --- /dev/null +++ b/subcmds/diff.py | |||
@@ -0,0 +1,27 @@ | |||
1 | # | ||
2 | # Copyright (C) 2008 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 PagedCommand | ||
17 | |||
18 | class Diff(PagedCommand): | ||
19 | common = True | ||
20 | helpSummary = "Show changes between commit and working tree" | ||
21 | helpUsage = """ | ||
22 | %prog [<project>...] | ||
23 | """ | ||
24 | |||
25 | def Execute(self, opt, args): | ||
26 | for project in self.GetProjects(args): | ||
27 | project.PrintWorkTreeDiff() | ||
diff --git a/subcmds/forall.py b/subcmds/forall.py new file mode 100644 index 00000000..b22e22a1 --- /dev/null +++ b/subcmds/forall.py | |||
@@ -0,0 +1,82 @@ | |||
1 | # | ||
2 | # Copyright (C) 2008 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 re | ||
17 | import os | ||
18 | import sys | ||
19 | import subprocess | ||
20 | from command import Command | ||
21 | |||
22 | class Forall(Command): | ||
23 | common = False | ||
24 | helpSummary = "Run a shell command in each project" | ||
25 | helpUsage = """ | ||
26 | %prog [<project>...] -c <command> [<arg>...] | ||
27 | """ | ||
28 | helpDescription = """ | ||
29 | Executes the same shell command in each project. | ||
30 | |||
31 | Environment | ||
32 | ----------- | ||
33 | pwd is the project's working directory. | ||
34 | |||
35 | REPO_PROJECT is set to the unique name of the project. | ||
36 | |||
37 | shell positional arguments ($1, $2, .., $#) are set to any arguments | ||
38 | following <command>. | ||
39 | |||
40 | stdin, stdout, stderr are inherited from the terminal and are | ||
41 | not redirected. | ||
42 | """ | ||
43 | |||
44 | def _Options(self, p): | ||
45 | def cmd(option, opt_str, value, parser): | ||
46 | setattr(parser.values, option.dest, list(parser.rargs)) | ||
47 | while parser.rargs: | ||
48 | del parser.rargs[0] | ||
49 | p.add_option('-c', '--command', | ||
50 | help='Command (and arguments) to execute', | ||
51 | dest='command', | ||
52 | action='callback', | ||
53 | callback=cmd) | ||
54 | |||
55 | def Execute(self, opt, args): | ||
56 | if not opt.command: | ||
57 | self.Usage() | ||
58 | |||
59 | cmd = [opt.command[0]] | ||
60 | |||
61 | shell = True | ||
62 | if re.compile(r'^[a-z0-9A-Z_/\.-]+$').match(cmd[0]): | ||
63 | shell = False | ||
64 | |||
65 | if shell: | ||
66 | cmd.append(cmd[0]) | ||
67 | cmd.extend(opt.command[1:]) | ||
68 | |||
69 | rc = 0 | ||
70 | for project in self.GetProjects(args): | ||
71 | env = dict(os.environ.iteritems()) | ||
72 | env['REPO_PROJECT'] = project.name | ||
73 | |||
74 | p = subprocess.Popen(cmd, | ||
75 | cwd = project.worktree, | ||
76 | shell = shell, | ||
77 | env = env) | ||
78 | r = p.wait() | ||
79 | if r != 0 and r != rc: | ||
80 | rc = r | ||
81 | if rc != 0: | ||
82 | sys.exit(rc) | ||
diff --git a/subcmds/help.py b/subcmds/help.py new file mode 100644 index 00000000..6e0238a0 --- /dev/null +++ b/subcmds/help.py | |||
@@ -0,0 +1,147 @@ | |||
1 | # | ||
2 | # Copyright (C) 2008 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 | ||
17 | from formatter import AbstractFormatter, DumbWriter | ||
18 | |||
19 | from color import Coloring | ||
20 | from command import PagedCommand | ||
21 | |||
22 | class Help(PagedCommand): | ||
23 | common = False | ||
24 | helpSummary = "Display detailed help on a command" | ||
25 | helpUsage = """ | ||
26 | %prog [--all|command] | ||
27 | """ | ||
28 | helpDescription = """ | ||
29 | Displays detailed usage information about a command. | ||
30 | """ | ||
31 | |||
32 | def _PrintAllCommands(self): | ||
33 | print 'usage: repo COMMAND [ARGS]' | ||
34 | print """ | ||
35 | The complete list of recognized repo commands are: | ||
36 | """ | ||
37 | commandNames = self.commands.keys() | ||
38 | commandNames.sort() | ||
39 | |||
40 | maxlen = 0 | ||
41 | for name in commandNames: | ||
42 | maxlen = max(maxlen, len(name)) | ||
43 | fmt = ' %%-%ds %%s' % maxlen | ||
44 | |||
45 | for name in commandNames: | ||
46 | command = self.commands[name] | ||
47 | try: | ||
48 | summary = command.helpSummary.strip() | ||
49 | except AttributeError: | ||
50 | summary = '' | ||
51 | print fmt % (name, summary) | ||
52 | print """ | ||
53 | See 'repo help <command>' for more information on a specific command. | ||
54 | """ | ||
55 | |||
56 | def _PrintCommonCommands(self): | ||
57 | print 'usage: repo COMMAND [ARGS]' | ||
58 | print """ | ||
59 | The most commonly used repo commands are: | ||
60 | """ | ||
61 | commandNames = [name | ||
62 | for name in self.commands.keys() | ||
63 | if self.commands[name].common] | ||
64 | commandNames.sort() | ||
65 | |||
66 | maxlen = 0 | ||
67 | for name in commandNames: | ||
68 | maxlen = max(maxlen, len(name)) | ||
69 | fmt = ' %%-%ds %%s' % maxlen | ||
70 | |||
71 | for name in commandNames: | ||
72 | command = self.commands[name] | ||
73 | try: | ||
74 | summary = command.helpSummary.strip() | ||
75 | except AttributeError: | ||
76 | summary = '' | ||
77 | print fmt % (name, summary) | ||
78 | print """ | ||
79 | See 'repo help <command>' for more information on a specific command. | ||
80 | """ | ||
81 | |||
82 | def _PrintCommandHelp(self, cmd): | ||
83 | class _Out(Coloring): | ||
84 | def __init__(self, gc): | ||
85 | Coloring.__init__(self, gc, 'help') | ||
86 | self.heading = self.printer('heading', attr='bold') | ||
87 | |||
88 | self.wrap = AbstractFormatter(DumbWriter()) | ||
89 | |||
90 | def _PrintSection(self, heading, bodyAttr): | ||
91 | try: | ||
92 | body = getattr(cmd, bodyAttr) | ||
93 | except AttributeError: | ||
94 | return | ||
95 | |||
96 | self.nl() | ||
97 | |||
98 | self.heading('%s', heading) | ||
99 | self.nl() | ||
100 | |||
101 | self.heading('%s', ''.ljust(len(heading), '-')) | ||
102 | self.nl() | ||
103 | |||
104 | me = 'repo %s' % cmd.NAME | ||
105 | body = body.strip() | ||
106 | body = body.replace('%prog', me) | ||
107 | |||
108 | for para in body.split("\n\n"): | ||
109 | if para.startswith(' '): | ||
110 | self.write('%s', para) | ||
111 | self.nl() | ||
112 | self.nl() | ||
113 | else: | ||
114 | self.wrap.add_flowing_data(para) | ||
115 | self.wrap.end_paragraph(1) | ||
116 | self.wrap.end_paragraph(0) | ||
117 | |||
118 | out = _Out(self.manifest.globalConfig) | ||
119 | cmd.OptionParser.print_help() | ||
120 | out._PrintSection('Summary', 'helpSummary') | ||
121 | out._PrintSection('Description', 'helpDescription') | ||
122 | |||
123 | def _Options(self, p): | ||
124 | p.add_option('-a', '--all', | ||
125 | dest='show_all', action='store_true', | ||
126 | help='show the complete list of commands') | ||
127 | |||
128 | def Execute(self, opt, args): | ||
129 | if len(args) == 0: | ||
130 | if opt.show_all: | ||
131 | self._PrintAllCommands() | ||
132 | else: | ||
133 | self._PrintCommonCommands() | ||
134 | |||
135 | elif len(args) == 1: | ||
136 | name = args[0] | ||
137 | |||
138 | try: | ||
139 | cmd = self.commands[name] | ||
140 | except KeyError: | ||
141 | print >>sys.stderr, "repo: '%s' is not a repo command." % name | ||
142 | sys.exit(1) | ||
143 | |||
144 | self._PrintCommandHelp(cmd) | ||
145 | |||
146 | else: | ||
147 | self._PrintCommandHelp(self) | ||
diff --git a/subcmds/init.py b/subcmds/init.py new file mode 100644 index 00000000..03f358d1 --- /dev/null +++ b/subcmds/init.py | |||
@@ -0,0 +1,193 @@ | |||
1 | # | ||
2 | # Copyright (C) 2008 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 os | ||
17 | import sys | ||
18 | |||
19 | from color import Coloring | ||
20 | from command import InteractiveCommand | ||
21 | from error import ManifestParseError | ||
22 | from remote import Remote | ||
23 | from git_command import git, MIN_GIT_VERSION | ||
24 | |||
25 | class Init(InteractiveCommand): | ||
26 | common = True | ||
27 | helpSummary = "Initialize repo in the current directory" | ||
28 | helpUsage = """ | ||
29 | %prog [options] | ||
30 | """ | ||
31 | helpDescription = """ | ||
32 | The '%prog' command is run once to install and initialize repo. | ||
33 | The latest repo source code and manifest collection is downloaded | ||
34 | from the server and is installed in the .repo/ directory in the | ||
35 | current working directory. | ||
36 | |||
37 | The optional <manifest> argument can be used to specify an alternate | ||
38 | manifest to be used. If no manifest is specified, the manifest | ||
39 | default.xml will be used. | ||
40 | """ | ||
41 | |||
42 | def _Options(self, p): | ||
43 | # Logging | ||
44 | g = p.add_option_group('Logging options') | ||
45 | g.add_option('-q', '--quiet', | ||
46 | dest="quiet", action="store_true", default=False, | ||
47 | help="be quiet") | ||
48 | |||
49 | # Manifest | ||
50 | g = p.add_option_group('Manifest options') | ||
51 | g.add_option('-u', '--manifest-url', | ||
52 | dest='manifest_url', | ||
53 | help='manifest repository location', metavar='URL') | ||
54 | g.add_option('-b', '--manifest-branch', | ||
55 | dest='manifest_branch', | ||
56 | help='manifest branch or revision', metavar='REVISION') | ||
57 | g.add_option('-m', '--manifest-name', | ||
58 | dest='manifest_name', default='default.xml', | ||
59 | help='initial manifest file', metavar='NAME.xml') | ||
60 | |||
61 | # Tool | ||
62 | g = p.add_option_group('Version options') | ||
63 | g.add_option('--repo-url', | ||
64 | dest='repo_url', | ||
65 | help='repo repository location', metavar='URL') | ||
66 | g.add_option('--repo-branch', | ||
67 | dest='repo_branch', | ||
68 | help='repo branch or revision', metavar='REVISION') | ||
69 | g.add_option('--no-repo-verify', | ||
70 | dest='no_repo_verify', action='store_true', | ||
71 | help='do not verify repo source code') | ||
72 | |||
73 | def _CheckGitVersion(self): | ||
74 | ver_str = git.version() | ||
75 | if not ver_str.startswith('git version '): | ||
76 | print >>sys.stderr, 'error: "%s" unsupported' % ver_str | ||
77 | sys.exit(1) | ||
78 | |||
79 | ver_str = ver_str[len('git version '):].strip() | ||
80 | ver_act = tuple(map(lambda x: int(x), ver_str.split('.')[0:3])) | ||
81 | if ver_act < MIN_GIT_VERSION: | ||
82 | need = '.'.join(map(lambda x: str(x), MIN_GIT_VERSION)) | ||
83 | print >>sys.stderr, 'fatal: git %s or later required' % need | ||
84 | sys.exit(1) | ||
85 | |||
86 | def _SyncManifest(self, opt): | ||
87 | m = self.manifest.manifestProject | ||
88 | |||
89 | if not m.Exists: | ||
90 | if not opt.manifest_url: | ||
91 | print >>sys.stderr, 'fatal: manifest url (-u) is required.' | ||
92 | sys.exit(1) | ||
93 | |||
94 | if not opt.quiet: | ||
95 | print >>sys.stderr, 'Getting manifest ...' | ||
96 | print >>sys.stderr, ' from %s' % opt.manifest_url | ||
97 | m._InitGitDir() | ||
98 | |||
99 | if opt.manifest_branch: | ||
100 | m.revision = opt.manifest_branch | ||
101 | else: | ||
102 | m.revision = 'refs/heads/master' | ||
103 | else: | ||
104 | if opt.manifest_branch: | ||
105 | m.revision = opt.manifest_branch | ||
106 | else: | ||
107 | m.PreSync() | ||
108 | |||
109 | if opt.manifest_url: | ||
110 | r = m.GetRemote(m.remote.name) | ||
111 | r.url = opt.manifest_url | ||
112 | r.ResetFetch() | ||
113 | r.Save() | ||
114 | |||
115 | m.Sync_NetworkHalf() | ||
116 | m.Sync_LocalHalf() | ||
117 | m.StartBranch('default') | ||
118 | |||
119 | def _LinkManifest(self, name): | ||
120 | if not name: | ||
121 | print >>sys.stderr, 'fatal: manifest name (-m) is required.' | ||
122 | sys.exit(1) | ||
123 | |||
124 | try: | ||
125 | self.manifest.Link(name) | ||
126 | except ManifestParseError, e: | ||
127 | print >>sys.stderr, "fatal: manifest '%s' not available" % name | ||
128 | print >>sys.stderr, 'fatal: %s' % str(e) | ||
129 | sys.exit(1) | ||
130 | |||
131 | def _PromptKey(self, prompt, key, value): | ||
132 | mp = self.manifest.manifestProject | ||
133 | |||
134 | sys.stdout.write('%-10s [%s]: ' % (prompt, value)) | ||
135 | a = sys.stdin.readline().strip() | ||
136 | if a != '' and a != value: | ||
137 | mp.config.SetString(key, a) | ||
138 | |||
139 | def _ConfigureUser(self): | ||
140 | mp = self.manifest.manifestProject | ||
141 | |||
142 | print '' | ||
143 | self._PromptKey('Your Name', 'user.name', mp.UserName) | ||
144 | self._PromptKey('Your Email', 'user.email', mp.UserEmail) | ||
145 | |||
146 | def _HasColorSet(self, gc): | ||
147 | for n in ['ui', 'diff', 'status']: | ||
148 | if gc.Has('color.%s' % n): | ||
149 | return True | ||
150 | return False | ||
151 | |||
152 | def _ConfigureColor(self): | ||
153 | gc = self.manifest.globalConfig | ||
154 | if self._HasColorSet(gc): | ||
155 | return | ||
156 | |||
157 | class _Test(Coloring): | ||
158 | def __init__(self): | ||
159 | Coloring.__init__(self, gc, 'test color display') | ||
160 | self._on = True | ||
161 | out = _Test() | ||
162 | |||
163 | print '' | ||
164 | print "Testing colorized output (for 'repo diff', 'repo status'):" | ||
165 | |||
166 | for c in ['black','red','green','yellow','blue','magenta','cyan']: | ||
167 | out.write(' ') | ||
168 | out.printer(fg=c)(' %-6s ', c) | ||
169 | out.write(' ') | ||
170 | out.printer(fg='white', bg='black')(' %s ' % 'white') | ||
171 | out.nl() | ||
172 | |||
173 | for c in ['bold','dim','ul','reverse']: | ||
174 | out.write(' ') | ||
175 | out.printer(fg='black', attr=c)(' %-6s ', c) | ||
176 | out.nl() | ||
177 | |||
178 | sys.stdout.write('Enable color display in this user account (y/n)? ') | ||
179 | a = sys.stdin.readline().strip().lower() | ||
180 | if a in ('y', 'yes', 't', 'true', 'on'): | ||
181 | gc.SetString('color.ui', 'auto') | ||
182 | |||
183 | def Execute(self, opt, args): | ||
184 | self._CheckGitVersion() | ||
185 | self._SyncManifest(opt) | ||
186 | self._LinkManifest(opt.manifest_name) | ||
187 | |||
188 | if os.isatty(0) and os.isatty(1): | ||
189 | self._ConfigureUser() | ||
190 | self._ConfigureColor() | ||
191 | |||
192 | print '' | ||
193 | print 'repo initialized in %s' % self.manifest.topdir | ||
diff --git a/subcmds/prune.py b/subcmds/prune.py new file mode 100644 index 00000000..f412bd48 --- /dev/null +++ b/subcmds/prune.py | |||
@@ -0,0 +1,59 @@ | |||
1 | # | ||
2 | # Copyright (C) 2008 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 | class Prune(PagedCommand): | ||
20 | common = True | ||
21 | helpSummary = "Prune (delete) already merged topics" | ||
22 | helpUsage = """ | ||
23 | %prog [<project>...] | ||
24 | """ | ||
25 | |||
26 | def Execute(self, opt, args): | ||
27 | all = [] | ||
28 | for project in self.GetProjects(args): | ||
29 | all.extend(project.PruneHeads()) | ||
30 | |||
31 | if not all: | ||
32 | return | ||
33 | |||
34 | class Report(Coloring): | ||
35 | def __init__(self, config): | ||
36 | Coloring.__init__(self, config, 'status') | ||
37 | self.project = self.printer('header', attr='bold') | ||
38 | |||
39 | out = Report(all[0].project.config) | ||
40 | out.project('Pending Branches') | ||
41 | out.nl() | ||
42 | |||
43 | project = None | ||
44 | |||
45 | for branch in all: | ||
46 | if project != branch.project: | ||
47 | project = branch.project | ||
48 | out.nl() | ||
49 | out.project('project %s/' % project.relpath) | ||
50 | out.nl() | ||
51 | |||
52 | commits = branch.commits | ||
53 | date = branch.date | ||
54 | print '%s %-33s (%2d commit%s, %s)' % ( | ||
55 | branch.name == project.CurrentBranch and '*' or ' ', | ||
56 | branch.name, | ||
57 | len(commits), | ||
58 | len(commits) != 1 and 's' or ' ', | ||
59 | date) | ||
diff --git a/subcmds/stage.py b/subcmds/stage.py new file mode 100644 index 00000000..c451cd6d --- /dev/null +++ b/subcmds/stage.py | |||
@@ -0,0 +1,108 @@ | |||
1 | # | ||
2 | # Copyright (C) 2008 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 | ||
17 | |||
18 | from color import Coloring | ||
19 | from command import InteractiveCommand | ||
20 | from git_command import GitCommand | ||
21 | |||
22 | class _ProjectList(Coloring): | ||
23 | def __init__(self, gc): | ||
24 | Coloring.__init__(self, gc, 'interactive') | ||
25 | self.prompt = self.printer('prompt', fg='blue', attr='bold') | ||
26 | self.header = self.printer('header', attr='bold') | ||
27 | self.help = self.printer('help', fg='red', attr='bold') | ||
28 | |||
29 | class Stage(InteractiveCommand): | ||
30 | common = True | ||
31 | helpSummary = "Stage file(s) for commit" | ||
32 | helpUsage = """ | ||
33 | %prog -i [<project>...] | ||
34 | """ | ||
35 | helpDescription = """ | ||
36 | The '%prog' command stages files to prepare the next commit. | ||
37 | """ | ||
38 | |||
39 | def _Options(self, p): | ||
40 | p.add_option('-i', '--interactive', | ||
41 | dest='interactive', action='store_true', | ||
42 | help='use interactive staging') | ||
43 | |||
44 | def Execute(self, opt, args): | ||
45 | if opt.interactive: | ||
46 | self._Interactive(opt, args) | ||
47 | else: | ||
48 | self.Usage() | ||
49 | |||
50 | def _Interactive(self, opt, args): | ||
51 | all = filter(lambda x: x.IsDirty(), self.GetProjects(args)) | ||
52 | if not all: | ||
53 | print >>sys.stderr,'no projects have uncommitted modifications' | ||
54 | return | ||
55 | |||
56 | out = _ProjectList(self.manifest.manifestProject.config) | ||
57 | while True: | ||
58 | out.header(' %-20s %s', 'project', 'path') | ||
59 | out.nl() | ||
60 | |||
61 | for i in xrange(0, len(all)): | ||
62 | p = all[i] | ||
63 | out.write('%3d: %-20s %s', i + 1, p.name, p.relpath + '/') | ||
64 | out.nl() | ||
65 | out.nl() | ||
66 | |||
67 | out.write('%3d: (', 0) | ||
68 | out.prompt('q') | ||
69 | out.write('uit)') | ||
70 | out.nl() | ||
71 | |||
72 | out.prompt('project> ') | ||
73 | try: | ||
74 | a = sys.stdin.readline() | ||
75 | except KeyboardInterrupt: | ||
76 | out.nl() | ||
77 | break | ||
78 | if a == '': | ||
79 | out.nl() | ||
80 | break | ||
81 | |||
82 | a = a.strip() | ||
83 | if a.lower() in ('q', 'quit', 'exit'): | ||
84 | break | ||
85 | if not a: | ||
86 | continue | ||
87 | |||
88 | try: | ||
89 | a_index = int(a) | ||
90 | except ValueError: | ||
91 | a_index = None | ||
92 | |||
93 | if a_index is not None: | ||
94 | if a_index == 0: | ||
95 | break | ||
96 | if 0 < a_index and a_index <= len(all): | ||
97 | _AddI(all[a_index - 1]) | ||
98 | continue | ||
99 | |||
100 | p = filter(lambda x: x.name == a or x.relpath == a, all) | ||
101 | if len(p) == 1: | ||
102 | _AddI(p[0]) | ||
103 | continue | ||
104 | print 'Bye.' | ||
105 | |||
106 | def _AddI(project): | ||
107 | p = GitCommand(project, ['add', '--interactive'], bare=False) | ||
108 | p.Wait() | ||
diff --git a/subcmds/start.py b/subcmds/start.py new file mode 100644 index 00000000..4eb3e476 --- /dev/null +++ b/subcmds/start.py | |||
@@ -0,0 +1,51 @@ | |||
1 | # | ||
2 | # Copyright (C) 2008 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 | ||
17 | from command import Command | ||
18 | from git_command import git | ||
19 | |||
20 | class Start(Command): | ||
21 | common = True | ||
22 | helpSummary = "Start a new branch for development" | ||
23 | helpUsage = """ | ||
24 | %prog <newbranchname> [<project>...] | ||
25 | |||
26 | This subcommand starts a new branch of development that is automatically | ||
27 | pulled from a remote branch. | ||
28 | |||
29 | It is equivalent to the following git commands: | ||
30 | |||
31 | "git branch --track <newbranchname> m/<codeline>", | ||
32 | or | ||
33 | "git checkout --track -b <newbranchname> m/<codeline>". | ||
34 | |||
35 | All three forms set up the config entries that repo bases some of its | ||
36 | processing on. Use %prog or git branch or checkout with --track to ensure | ||
37 | the configuration data is set up properly. | ||
38 | |||
39 | """ | ||
40 | |||
41 | def Execute(self, opt, args): | ||
42 | if not args: | ||
43 | self.Usage() | ||
44 | |||
45 | nb = args[0] | ||
46 | if not git.check_ref_format('heads/%s' % nb): | ||
47 | print >>sys.stderr, "error: '%s' is not a valid name" % nb | ||
48 | sys.exit(1) | ||
49 | |||
50 | for project in self.GetProjects(args[1:]): | ||
51 | project.StartBranch(nb) | ||
diff --git a/subcmds/status.py b/subcmds/status.py new file mode 100644 index 00000000..1615b423 --- /dev/null +++ b/subcmds/status.py | |||
@@ -0,0 +1,27 @@ | |||
1 | # | ||
2 | # Copyright (C) 2008 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 PagedCommand | ||
17 | |||
18 | class Status(PagedCommand): | ||
19 | common = True | ||
20 | helpSummary = "Show the working tree status" | ||
21 | helpUsage = """ | ||
22 | %prog [<project>...] | ||
23 | """ | ||
24 | |||
25 | def Execute(self, opt, args): | ||
26 | for project in self.GetProjects(args): | ||
27 | project.PrintWorkTreeStatus() | ||
diff --git a/subcmds/sync.py b/subcmds/sync.py new file mode 100644 index 00000000..3eb44edf --- /dev/null +++ b/subcmds/sync.py | |||
@@ -0,0 +1,150 @@ | |||
1 | # | ||
2 | # Copyright (C) 2008 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 os | ||
17 | import re | ||
18 | import subprocess | ||
19 | import sys | ||
20 | |||
21 | from git_command import GIT | ||
22 | from command import Command | ||
23 | from error import RepoChangedException, GitError | ||
24 | from project import R_HEADS | ||
25 | |||
26 | class Sync(Command): | ||
27 | common = True | ||
28 | helpSummary = "Update working tree to the latest revision" | ||
29 | helpUsage = """ | ||
30 | %prog [<project>...] | ||
31 | """ | ||
32 | helpDescription = """ | ||
33 | The '%prog' command synchronizes local project directories | ||
34 | with the remote repositories specified in the manifest. If a local | ||
35 | project does not yet exist, it will clone a new local directory from | ||
36 | the remote repository and set up tracking branches as specified in | ||
37 | the manifest. If the local project already exists, '%prog' | ||
38 | will update the remote branches and rebase any new local changes | ||
39 | on top of the new remote changes. | ||
40 | |||
41 | '%prog' will synchronize all projects listed at the command | ||
42 | line. Projects can be specified either by name, or by a relative | ||
43 | or absolute path to the project's local directory. If no projects | ||
44 | are specified, '%prog' will synchronize all projects listed in | ||
45 | the manifest. | ||
46 | """ | ||
47 | |||
48 | def _Options(self, p): | ||
49 | p.add_option('--no-repo-verify', | ||
50 | dest='no_repo_verify', action='store_true', | ||
51 | help='do not verify repo source code') | ||
52 | |||
53 | def _Fetch(self, *projects): | ||
54 | fetched = set() | ||
55 | for project in projects: | ||
56 | if project.Sync_NetworkHalf(): | ||
57 | fetched.add(project.gitdir) | ||
58 | else: | ||
59 | print >>sys.stderr, 'error: Cannot fetch %s' % project.name | ||
60 | sys.exit(1) | ||
61 | return fetched | ||
62 | |||
63 | def Execute(self, opt, args): | ||
64 | rp = self.manifest.repoProject | ||
65 | rp.PreSync() | ||
66 | |||
67 | mp = self.manifest.manifestProject | ||
68 | mp.PreSync() | ||
69 | |||
70 | all = self.GetProjects(args, missing_ok=True) | ||
71 | fetched = self._Fetch(rp, mp, *all) | ||
72 | |||
73 | if rp.HasChanges: | ||
74 | print >>sys.stderr, 'info: A new version of repo is available' | ||
75 | print >>sys.stderr, '' | ||
76 | if opt.no_repo_verify or _VerifyTag(rp): | ||
77 | if not rp.Sync_LocalHalf(): | ||
78 | sys.exit(1) | ||
79 | print >>sys.stderr, 'info: Restarting repo with latest version' | ||
80 | raise RepoChangedException() | ||
81 | else: | ||
82 | print >>sys.stderr, 'warning: Skipped upgrade to unverified version' | ||
83 | |||
84 | if mp.HasChanges: | ||
85 | if not mp.Sync_LocalHalf(): | ||
86 | sys.exit(1) | ||
87 | |||
88 | self.manifest._Unload() | ||
89 | all = self.GetProjects(args, missing_ok=True) | ||
90 | missing = [] | ||
91 | for project in all: | ||
92 | if project.gitdir not in fetched: | ||
93 | missing.append(project) | ||
94 | self._Fetch(*missing) | ||
95 | |||
96 | for project in all: | ||
97 | if not project.Sync_LocalHalf(): | ||
98 | sys.exit(1) | ||
99 | |||
100 | |||
101 | def _VerifyTag(project): | ||
102 | gpg_dir = os.path.expanduser('~/.repoconfig/gnupg') | ||
103 | if not os.path.exists(gpg_dir): | ||
104 | print >>sys.stderr,\ | ||
105 | """warning: GnuPG was not available during last "repo init" | ||
106 | warning: Cannot automatically authenticate repo.""" | ||
107 | return True | ||
108 | |||
109 | remote = project.GetRemote(project.remote.name) | ||
110 | ref = remote.ToLocal(project.revision) | ||
111 | |||
112 | try: | ||
113 | cur = project.bare_git.describe(ref) | ||
114 | except GitError: | ||
115 | cur = None | ||
116 | |||
117 | if not cur \ | ||
118 | or re.compile(r'^.*-[0-9]{1,}-g[0-9a-f]{1,}$').match(cur): | ||
119 | rev = project.revision | ||
120 | if rev.startswith(R_HEADS): | ||
121 | rev = rev[len(R_HEADS):] | ||
122 | |||
123 | print >>sys.stderr | ||
124 | print >>sys.stderr,\ | ||
125 | "warning: project '%s' branch '%s' is not signed" \ | ||
126 | % (project.name, rev) | ||
127 | return False | ||
128 | |||
129 | env = dict(os.environ) | ||
130 | env['GIT_DIR'] = project.gitdir | ||
131 | env['GNUPGHOME'] = gpg_dir | ||
132 | |||
133 | cmd = [GIT, 'tag', '-v', cur] | ||
134 | proc = subprocess.Popen(cmd, | ||
135 | stdout = subprocess.PIPE, | ||
136 | stderr = subprocess.PIPE, | ||
137 | env = env) | ||
138 | out = proc.stdout.read() | ||
139 | proc.stdout.close() | ||
140 | |||
141 | err = proc.stderr.read() | ||
142 | proc.stderr.close() | ||
143 | |||
144 | if proc.wait() != 0: | ||
145 | print >>sys.stderr | ||
146 | print >>sys.stderr, out | ||
147 | print >>sys.stderr, err | ||
148 | print >>sys.stderr | ||
149 | return False | ||
150 | return True | ||
diff --git a/subcmds/upload.py b/subcmds/upload.py new file mode 100644 index 00000000..ad05050e --- /dev/null +++ b/subcmds/upload.py | |||
@@ -0,0 +1,180 @@ | |||
1 | # | ||
2 | # Copyright (C) 2008 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 re | ||
17 | import sys | ||
18 | |||
19 | from command import InteractiveCommand | ||
20 | from editor import Editor | ||
21 | from error import UploadError | ||
22 | |||
23 | def _die(fmt, *args): | ||
24 | msg = fmt % args | ||
25 | print >>sys.stderr, 'error: %s' % msg | ||
26 | sys.exit(1) | ||
27 | |||
28 | class Upload(InteractiveCommand): | ||
29 | common = True | ||
30 | helpSummary = "Upload changes for code review" | ||
31 | helpUsage=""" | ||
32 | %prog [<project>]... | ||
33 | """ | ||
34 | helpDescription = """ | ||
35 | The '%prog' command is used to send changes to the Gerrit code | ||
36 | review system. It searches for changes in local projects that do | ||
37 | not yet exist in the corresponding remote repository. If multiple | ||
38 | changes are found, '%prog' opens an editor to allow the | ||
39 | user to choose which change to upload. After a successful upload, | ||
40 | repo prints the URL for the change in the Gerrit code review system. | ||
41 | |||
42 | '%prog' searches for uploadable changes in all projects listed | ||
43 | at the command line. Projects can be specified either by name, or | ||
44 | by a relative or absolute path to the project's local directory. If | ||
45 | no projects are specified, '%prog' will search for uploadable | ||
46 | changes in all projects listed in the manifest. | ||
47 | """ | ||
48 | |||
49 | def _SingleBranch(self, branch): | ||
50 | project = branch.project | ||
51 | name = branch.name | ||
52 | date = branch.date | ||
53 | list = branch.commits | ||
54 | |||
55 | print 'Upload project %s/:' % project.relpath | ||
56 | print ' branch %s (%2d commit%s, %s):' % ( | ||
57 | name, | ||
58 | len(list), | ||
59 | len(list) != 1 and 's' or '', | ||
60 | date) | ||
61 | for commit in list: | ||
62 | print ' %s' % commit | ||
63 | |||
64 | sys.stdout.write('(y/n)? ') | ||
65 | answer = sys.stdin.readline().strip() | ||
66 | if answer in ('y', 'Y', 'yes', '1', 'true', 't'): | ||
67 | self._UploadAndReport([branch]) | ||
68 | else: | ||
69 | _die("upload aborted by user") | ||
70 | |||
71 | def _MultipleBranches(self, pending): | ||
72 | projects = {} | ||
73 | branches = {} | ||
74 | |||
75 | script = [] | ||
76 | script.append('# Uncomment the branches to upload:') | ||
77 | for project, avail in pending: | ||
78 | script.append('#') | ||
79 | script.append('# project %s/:' % project.relpath) | ||
80 | |||
81 | b = {} | ||
82 | for branch in avail: | ||
83 | name = branch.name | ||
84 | date = branch.date | ||
85 | list = branch.commits | ||
86 | |||
87 | if b: | ||
88 | script.append('#') | ||
89 | script.append('# branch %s (%2d commit%s, %s):' % ( | ||
90 | name, | ||
91 | len(list), | ||
92 | len(list) != 1 and 's' or '', | ||
93 | date)) | ||
94 | for commit in list: | ||
95 | script.append('# %s' % commit) | ||
96 | b[name] = branch | ||
97 | |||
98 | projects[project.relpath] = project | ||
99 | branches[project.name] = b | ||
100 | script.append('') | ||
101 | |||
102 | script = Editor.EditString("\n".join(script)).split("\n") | ||
103 | |||
104 | project_re = re.compile(r'^#?\s*project\s*([^\s]+)/:$') | ||
105 | branch_re = re.compile(r'^\s*branch\s*([^\s(]+)\s*\(.*') | ||
106 | |||
107 | project = None | ||
108 | todo = [] | ||
109 | |||
110 | for line in script: | ||
111 | m = project_re.match(line) | ||
112 | if m: | ||
113 | name = m.group(1) | ||
114 | project = projects.get(name) | ||
115 | if not project: | ||
116 | _die('project %s not available for upload', name) | ||
117 | continue | ||
118 | |||
119 | m = branch_re.match(line) | ||
120 | if m: | ||
121 | name = m.group(1) | ||
122 | if not project: | ||
123 | _die('project for branch %s not in script', name) | ||
124 | branch = branches[project.name].get(name) | ||
125 | if not branch: | ||
126 | _die('branch %s not in %s', name, project.relpath) | ||
127 | todo.append(branch) | ||
128 | if not todo: | ||
129 | _die("nothing uncommented for upload") | ||
130 | self._UploadAndReport(todo) | ||
131 | |||
132 | def _UploadAndReport(self, todo): | ||
133 | have_errors = False | ||
134 | for branch in todo: | ||
135 | try: | ||
136 | branch.UploadForReview() | ||
137 | branch.uploaded = True | ||
138 | except UploadError, e: | ||
139 | branch.error = e | ||
140 | branch.uploaded = False | ||
141 | have_errors = True | ||
142 | |||
143 | print >>sys.stderr, '' | ||
144 | print >>sys.stderr, '--------------------------------------------' | ||
145 | |||
146 | if have_errors: | ||
147 | for branch in todo: | ||
148 | if not branch.uploaded: | ||
149 | print >>sys.stderr, '[FAILED] %-15s %-15s (%s)' % ( | ||
150 | branch.project.relpath + '/', \ | ||
151 | branch.name, \ | ||
152 | branch.error) | ||
153 | print >>sys.stderr, '' | ||
154 | |||
155 | for branch in todo: | ||
156 | if branch.uploaded: | ||
157 | print >>sys.stderr, '[OK ] %-15s %s' % ( | ||
158 | branch.project.relpath + '/', | ||
159 | branch.name) | ||
160 | print >>sys.stderr, '%s' % branch.tip_url | ||
161 | print >>sys.stderr, '' | ||
162 | |||
163 | if have_errors: | ||
164 | sys.exit(1) | ||
165 | |||
166 | def Execute(self, opt, args): | ||
167 | project_list = self.GetProjects(args) | ||
168 | pending = [] | ||
169 | |||
170 | for project in project_list: | ||
171 | avail = project.GetUploadableBranches() | ||
172 | if avail: | ||
173 | pending.append((project, avail)) | ||
174 | |||
175 | if not pending: | ||
176 | print >>sys.stdout, "no branches ready for upload" | ||
177 | elif len(pending) == 1 and len(pending[0][1]) == 1: | ||
178 | self._SingleBranch(pending[0][1][0]) | ||
179 | else: | ||
180 | self._MultipleBranches(pending) | ||