summaryrefslogtreecommitdiffstats
path: root/subcmds
diff options
context:
space:
mode:
Diffstat (limited to 'subcmds')
-rw-r--r--subcmds/__init__.py49
-rw-r--r--subcmds/compute_snapshot_check.py169
-rw-r--r--subcmds/diff.py27
-rw-r--r--subcmds/forall.py82
-rw-r--r--subcmds/help.py147
-rw-r--r--subcmds/init.py193
-rw-r--r--subcmds/prune.py59
-rw-r--r--subcmds/stage.py108
-rw-r--r--subcmds/start.py51
-rw-r--r--subcmds/status.py27
-rw-r--r--subcmds/sync.py150
-rw-r--r--subcmds/upload.py180
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
16import os
17
18all = {}
19
20my_dir = os.path.dirname(__file__)
21for 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
48if '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
16import os
17import sys
18import tempfile
19
20from command import Command
21from error import GitError, NoSuchProjectError
22from git_config import IsId
23from import_tar import ImportTar
24from import_zip import ImportZip
25from project import Project
26from remote import Remote
27
28def _ToCommit(project, rev):
29 return project.bare_git.rev_parse('--verify', '%s^0' % rev)
30
31def _Missing(project, rev):
32 return project._revlist('--objects', rev, '--not', '--all')
33
34
35class 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
43snapshot, 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
16from command import PagedCommand
17
18class 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
16import re
17import os
18import sys
19import subprocess
20from command import Command
21
22class 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 = """
29Executes the same shell command in each project.
30
31Environment
32-----------
33pwd is the project's working directory.
34
35REPO_PROJECT is set to the unique name of the project.
36
37shell positional arguments ($1, $2, .., $#) are set to any arguments
38following <command>.
39
40stdin, stdout, stderr are inherited from the terminal and are
41not 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
16import sys
17from formatter import AbstractFormatter, DumbWriter
18
19from color import Coloring
20from command import PagedCommand
21
22class Help(PagedCommand):
23 common = False
24 helpSummary = "Display detailed help on a command"
25 helpUsage = """
26%prog [--all|command]
27"""
28 helpDescription = """
29Displays detailed usage information about a command.
30"""
31
32 def _PrintAllCommands(self):
33 print 'usage: repo COMMAND [ARGS]'
34 print """
35The 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 """
53See 'repo help <command>' for more information on a specific command.
54"""
55
56 def _PrintCommonCommands(self):
57 print 'usage: repo COMMAND [ARGS]'
58 print """
59The 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 """
79See '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
16import os
17import sys
18
19from color import Coloring
20from command import InteractiveCommand
21from error import ManifestParseError
22from remote import Remote
23from git_command import git, MIN_GIT_VERSION
24
25class Init(InteractiveCommand):
26 common = True
27 helpSummary = "Initialize repo in the current directory"
28 helpUsage = """
29%prog [options]
30"""
31 helpDescription = """
32The '%prog' command is run once to install and initialize repo.
33The latest repo source code and manifest collection is downloaded
34from the server and is installed in the .repo/ directory in the
35current working directory.
36
37The optional <manifest> argument can be used to specify an alternate
38manifest to be used. If no manifest is specified, the manifest
39default.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
16from color import Coloring
17from command import PagedCommand
18
19class 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
16import sys
17
18from color import Coloring
19from command import InteractiveCommand
20from git_command import GitCommand
21
22class _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
29class Stage(InteractiveCommand):
30 common = True
31 helpSummary = "Stage file(s) for commit"
32 helpUsage = """
33%prog -i [<project>...]
34"""
35 helpDescription = """
36The '%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
106def _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
16import sys
17from command import Command
18from git_command import git
19
20class Start(Command):
21 common = True
22 helpSummary = "Start a new branch for development"
23 helpUsage = """
24%prog <newbranchname> [<project>...]
25
26This subcommand starts a new branch of development that is automatically
27pulled from a remote branch.
28
29It is equivalent to the following git commands:
30
31"git branch --track <newbranchname> m/<codeline>",
32or
33"git checkout --track -b <newbranchname> m/<codeline>".
34
35All three forms set up the config entries that repo bases some of its
36processing on. Use %prog or git branch or checkout with --track to ensure
37the 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
16from command import PagedCommand
17
18class 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
16import os
17import re
18import subprocess
19import sys
20
21from git_command import GIT
22from command import Command
23from error import RepoChangedException, GitError
24from project import R_HEADS
25
26class Sync(Command):
27 common = True
28 helpSummary = "Update working tree to the latest revision"
29 helpUsage = """
30%prog [<project>...]
31"""
32 helpDescription = """
33The '%prog' command synchronizes local project directories
34with the remote repositories specified in the manifest. If a local
35project does not yet exist, it will clone a new local directory from
36the remote repository and set up tracking branches as specified in
37the manifest. If the local project already exists, '%prog'
38will update the remote branches and rebase any new local changes
39on top of the new remote changes.
40
41'%prog' will synchronize all projects listed at the command
42line. Projects can be specified either by name, or by a relative
43or absolute path to the project's local directory. If no projects
44are specified, '%prog' will synchronize all projects listed in
45the 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
101def _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"
106warning: 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
16import re
17import sys
18
19from command import InteractiveCommand
20from editor import Editor
21from error import UploadError
22
23def _die(fmt, *args):
24 msg = fmt % args
25 print >>sys.stderr, 'error: %s' % msg
26 sys.exit(1)
27
28class Upload(InteractiveCommand):
29 common = True
30 helpSummary = "Upload changes for code review"
31 helpUsage="""
32%prog [<project>]...
33"""
34 helpDescription = """
35The '%prog' command is used to send changes to the Gerrit code
36review system. It searches for changes in local projects that do
37not yet exist in the corresponding remote repository. If multiple
38changes are found, '%prog' opens an editor to allow the
39user to choose which change to upload. After a successful upload,
40repo prints the URL for the change in the Gerrit code review system.
41
42'%prog' searches for uploadable changes in all projects listed
43at the command line. Projects can be specified either by name, or
44by a relative or absolute path to the project's local directory. If
45no projects are specified, '%prog' will search for uploadable
46changes 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)