summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.mailmap8
-rw-r--r--.pylintrc2
-rw-r--r--command.py38
-rw-r--r--docs/manifest-format.txt6
-rw-r--r--gitc_utils.py2
-rwxr-xr-xhooks/commit-msg19
-rwxr-xr-xmain.py2
-rw-r--r--manifest_xml.py15
-rw-r--r--project.py286
-rwxr-xr-xrepo2
-rw-r--r--subcmds/diffmanifests.py21
-rw-r--r--subcmds/forall.py11
-rw-r--r--subcmds/sync.py2
13 files changed, 254 insertions, 160 deletions
diff --git a/.mailmap b/.mailmap
new file mode 100644
index 00000000..f070b494
--- /dev/null
+++ b/.mailmap
@@ -0,0 +1,8 @@
1Anthony Newnam <anthony.newnam@garmin.com> Anthony <anthony@bnovc.com>
2Shawn Pearce <sop@google.com> Shawn O. Pearce <sop@google.com>
3Jia Bi <bijia@xiaomi.com> bijia <bijia@xiaomi.com>
4JoonCheol Park <jooncheol@gmail.com> Jooncheol Park <jooncheol@gmail.com>
5Sergii Pylypenko <x.pelya.x@gmail.com> pelya <x.pelya.x@gmail.com>
6Ulrik Sjölin <ulrik.sjolin@sonyericsson.com> Ulrik Sjolin <ulrik.sjolin@gmail.com>
7Ulrik Sjölin <ulrik.sjolin@sonyericsson.com> Ulrik Sjolin <ulrik.sjolin@sonyericsson.com>
8Ulrik Sjölin <ulrik.sjolin@sonyericsson.com> Ulrik Sjölin <ulrik.sjolin@sonyericsson.com>
diff --git a/.pylintrc b/.pylintrc
index c6be7436..413d66a1 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -53,7 +53,7 @@ load-plugins=
53enable=RP0004 53enable=RP0004
54 54
55# Disable the message(s) with the given id(s). 55# Disable the message(s) with the given id(s).
56disable=R0903,R0912,R0913,R0914,R0915,W0141,C0111,C0103,W0603,W0703,R0911,C0301,C0302,R0902,R0904,W0142,W0212,E1101,E1103,R0201,W0201,W0122,W0232,RP0001,RP0003,RP0101,RP0002,RP0401,RP0701,RP0801,F0401,E0611,R0801,I0011 56disable=C0326,R0903,R0912,R0913,R0914,R0915,W0141,C0111,C0103,W0603,W0703,R0911,C0301,C0302,R0902,R0904,W0142,W0212,E1101,E1103,R0201,W0201,W0122,W0232,RP0001,RP0003,RP0101,RP0002,RP0401,RP0701,RP0801,F0401,E0611,R0801,I0011
57 57
58[REPORTS] 58[REPORTS]
59 59
diff --git a/command.py b/command.py
index cd5e3c3e..bc2f9501 100644
--- a/command.py
+++ b/command.py
@@ -31,7 +31,7 @@ class Command(object):
31 manifest = None 31 manifest = None
32 _optparse = None 32 _optparse = None
33 33
34 def WantPager(self, opt): 34 def WantPager(self, _opt):
35 return False 35 return False
36 36
37 def ReadEnvironmentOptions(self, opts): 37 def ReadEnvironmentOptions(self, opts):
@@ -63,7 +63,7 @@ class Command(object):
63 usage = self.helpUsage.strip().replace('%prog', me) 63 usage = self.helpUsage.strip().replace('%prog', me)
64 except AttributeError: 64 except AttributeError:
65 usage = 'repo %s' % self.NAME 65 usage = 'repo %s' % self.NAME
66 self._optparse = optparse.OptionParser(usage = usage) 66 self._optparse = optparse.OptionParser(usage=usage)
67 self._Options(self._optparse) 67 self._Options(self._optparse)
68 return self._optparse 68 return self._optparse
69 69
@@ -110,9 +110,9 @@ class Command(object):
110 project = None 110 project = None
111 if os.path.exists(path): 111 if os.path.exists(path):
112 oldpath = None 112 oldpath = None
113 while path \ 113 while path and \
114 and path != oldpath \ 114 path != oldpath and \
115 and path != manifest.topdir: 115 path != manifest.topdir:
116 try: 116 try:
117 project = self._by_path[path] 117 project = self._by_path[path]
118 break 118 break
@@ -138,7 +138,7 @@ class Command(object):
138 mp = manifest.manifestProject 138 mp = manifest.manifestProject
139 139
140 if not groups: 140 if not groups:
141 groups = mp.config.GetString('manifest.groups') 141 groups = mp.config.GetString('manifest.groups')
142 if not groups: 142 if not groups:
143 groups = 'default,platform-' + platform.system().lower() 143 groups = 'default,platform-' + platform.system().lower()
144 groups = [x for x in re.split(r'[,\s]+', groups) if x] 144 groups = [x for x in re.split(r'[,\s]+', groups) if x]
@@ -151,8 +151,7 @@ class Command(object):
151 for p in project.GetDerivedSubprojects()) 151 for p in project.GetDerivedSubprojects())
152 all_projects_list.extend(derived_projects.values()) 152 all_projects_list.extend(derived_projects.values())
153 for project in all_projects_list: 153 for project in all_projects_list:
154 if ((missing_ok or project.Exists) and 154 if (missing_ok or project.Exists) and project.MatchesGroups(groups):
155 project.MatchesGroups(groups)):
156 result.append(project) 155 result.append(project)
157 else: 156 else:
158 self._ResetPathToProjectMap(all_projects_list) 157 self._ResetPathToProjectMap(all_projects_list)
@@ -166,8 +165,8 @@ class Command(object):
166 165
167 # If it's not a derived project, update path->project mapping and 166 # If it's not a derived project, update path->project mapping and
168 # search again, as arg might actually point to a derived subproject. 167 # search again, as arg might actually point to a derived subproject.
169 if (project and not project.Derived and 168 if (project and not project.Derived and (submodules_ok or
170 (submodules_ok or project.sync_s)): 169 project.sync_s)):
171 search_again = False 170 search_again = False
172 for subproject in project.GetDerivedSubprojects(): 171 for subproject in project.GetDerivedSubprojects():
173 self._UpdatePathToProjectMap(subproject) 172 self._UpdatePathToProjectMap(subproject)
@@ -194,17 +193,24 @@ class Command(object):
194 result.sort(key=_getpath) 193 result.sort(key=_getpath)
195 return result 194 return result
196 195
197 def FindProjects(self, args): 196 def FindProjects(self, args, inverse=False):
198 result = [] 197 result = []
199 patterns = [re.compile(r'%s' % a, re.IGNORECASE) for a in args] 198 patterns = [re.compile(r'%s' % a, re.IGNORECASE) for a in args]
200 for project in self.GetProjects(''): 199 for project in self.GetProjects(''):
201 for pattern in patterns: 200 for pattern in patterns:
202 if pattern.search(project.name) or pattern.search(project.relpath): 201 match = pattern.search(project.name) or pattern.search(project.relpath)
202 if not inverse and match:
203 result.append(project) 203 result.append(project)
204 break 204 break
205 if inverse and match:
206 break
207 else:
208 if inverse:
209 result.append(project)
205 result.sort(key=lambda project: project.relpath) 210 result.sort(key=lambda project: project.relpath)
206 return result 211 return result
207 212
213
208# pylint: disable=W0223 214# pylint: disable=W0223
209# Pylint warns that the `InteractiveCommand` and `PagedCommand` classes do not 215# Pylint warns that the `InteractiveCommand` and `PagedCommand` classes do not
210# override method `Execute` which is abstract in `Command`. Since that method 216# override method `Execute` which is abstract in `Command`. Since that method
@@ -214,28 +220,32 @@ class InteractiveCommand(Command):
214 """Command which requires user interaction on the tty and 220 """Command which requires user interaction on the tty and
215 must not run within a pager, even if the user asks to. 221 must not run within a pager, even if the user asks to.
216 """ 222 """
217 def WantPager(self, opt): 223 def WantPager(self, _opt):
218 return False 224 return False
219 225
226
220class PagedCommand(Command): 227class PagedCommand(Command):
221 """Command which defaults to output in a pager, as its 228 """Command which defaults to output in a pager, as its
222 display tends to be larger than one screen full. 229 display tends to be larger than one screen full.
223 """ 230 """
224 def WantPager(self, opt): 231 def WantPager(self, _opt):
225 return True 232 return True
226 233
227# pylint: enable=W0223 234# pylint: enable=W0223
228 235
236
229class MirrorSafeCommand(object): 237class MirrorSafeCommand(object):
230 """Command permits itself to run within a mirror, 238 """Command permits itself to run within a mirror,
231 and does not require a working directory. 239 and does not require a working directory.
232 """ 240 """
233 241
242
234class GitcAvailableCommand(object): 243class GitcAvailableCommand(object):
235 """Command that requires GITC to be available, but does 244 """Command that requires GITC to be available, but does
236 not require the local client to be a GITC client. 245 not require the local client to be a GITC client.
237 """ 246 """
238 247
248
239class GitcClientCommand(object): 249class GitcClientCommand(object):
240 """Command that requires the local client to be a GITC 250 """Command that requires the local client to be a GITC
241 client. 251 client.
diff --git a/docs/manifest-format.txt b/docs/manifest-format.txt
index 140a782f..8fd9137c 100644
--- a/docs/manifest-format.txt
+++ b/docs/manifest-format.txt
@@ -175,7 +175,8 @@ The manifest server should implement the following RPC methods:
175 GetApprovedManifest(branch, target) 175 GetApprovedManifest(branch, target)
176 176
177Return a manifest in which each project is pegged to a known good revision 177Return a manifest in which each project is pegged to a known good revision
178for the current branch and target. 178for the current branch and target. This is used by repo sync when the
179--smart-sync option is given.
179 180
180The target to use is defined by environment variables TARGET_PRODUCT 181The target to use is defined by environment variables TARGET_PRODUCT
181and TARGET_BUILD_VARIANT. These variables are used to create a string 182and TARGET_BUILD_VARIANT. These variables are used to create a string
@@ -187,7 +188,8 @@ should choose a reasonable default target.
187 GetManifest(tag) 188 GetManifest(tag)
188 189
189Return a manifest in which each project is pegged to the revision at 190Return a manifest in which each project is pegged to the revision at
190the specified tag. 191the specified tag. This is used by repo sync when the --smart-tag option
192is given.
191 193
192 194
193Element project 195Element project
diff --git a/gitc_utils.py b/gitc_utils.py
index 0f3e1818..a388dc27 100644
--- a/gitc_utils.py
+++ b/gitc_utils.py
@@ -127,7 +127,7 @@ def generate_gitc_manifest(gitc_manifest, manifest, paths=None):
127 repo_proj.revisionExpr = None 127 repo_proj.revisionExpr = None
128 128
129 # Convert URLs from relative to absolute. 129 # Convert URLs from relative to absolute.
130 for name, remote in manifest.remotes.iteritems(): 130 for _name, remote in manifest.remotes.iteritems():
131 remote.fetchUrl = remote.resolvedFetchUrl 131 remote.fetchUrl = remote.resolvedFetchUrl
132 132
133 # Save the manifest. 133 # Save the manifest.
diff --git a/hooks/commit-msg b/hooks/commit-msg
index d8f009b6..40ac237a 100755
--- a/hooks/commit-msg
+++ b/hooks/commit-msg
@@ -1,6 +1,7 @@
1#!/bin/sh 1#!/bin/sh
2# From Gerrit Code Review 2.12.1
2# 3#
3# Part of Gerrit Code Review (http://code.google.com/p/gerrit/) 4# Part of Gerrit Code Review (https://www.gerritcodereview.com/)
4# 5#
5# Copyright (C) 2009 The Android Open Source Project 6# Copyright (C) 2009 The Android Open Source Project
6# 7#
@@ -19,7 +20,7 @@
19 20
20unset GREP_OPTIONS 21unset GREP_OPTIONS
21 22
22CHANGE_ID_AFTER="Bug|Issue" 23CHANGE_ID_AFTER="Bug|Issue|Test"
23MSG="$1" 24MSG="$1"
24 25
25# Check for, and add if missing, a unique Change-Id 26# Check for, and add if missing, a unique Change-Id
@@ -38,6 +39,12 @@ add_ChangeId() {
38 return 39 return
39 fi 40 fi
40 41
42 # Do not add Change-Id to temp commits
43 if echo "$clean_message" | head -1 | grep -q '^\(fixup\|squash\)!'
44 then
45 return
46 fi
47
41 if test "false" = "`git config --bool --get gerrit.createChangeId`" 48 if test "false" = "`git config --bool --get gerrit.createChangeId`"
42 then 49 then
43 return 50 return
@@ -57,6 +64,10 @@ add_ChangeId() {
57 AWK=/usr/xpg4/bin/awk 64 AWK=/usr/xpg4/bin/awk
58 fi 65 fi
59 66
67 # Get core.commentChar from git config or use default symbol
68 commentChar=`git config --get core.commentChar`
69 commentChar=${commentChar:-#}
70
60 # How this works: 71 # How this works:
61 # - parse the commit message as (textLine+ blankLine*)* 72 # - parse the commit message as (textLine+ blankLine*)*
62 # - assume textLine+ to be a footer until proven otherwise 73 # - assume textLine+ to be a footer until proven otherwise
@@ -75,8 +86,8 @@ add_ChangeId() {
75 blankLines = 0 86 blankLines = 0
76 } 87 }
77 88
78 # Skip lines starting with "#" without any spaces before it. 89 # Skip lines starting with commentChar without any spaces before it.
79 /^#/ { next } 90 /^'"$commentChar"'/ { next }
80 91
81 # Skip the line starting with the diff command and everything after it, 92 # Skip the line starting with the diff command and everything after it,
82 # up to the end of the file, assuming it is only patch data. 93 # up to the end of the file, assuming it is only patch data.
diff --git a/main.py b/main.py
index 4f4eb9fc..c5f1e9c3 100755
--- a/main.py
+++ b/main.py
@@ -379,7 +379,7 @@ class _KerberosAuthHandler(urllib.request.BaseHandler):
379 self.context = None 379 self.context = None
380 self.handler_order = urllib.request.BaseHandler.handler_order - 50 380 self.handler_order = urllib.request.BaseHandler.handler_order - 50
381 381
382 def http_error_401(self, req, fp, code, msg, headers): 382 def http_error_401(self, req, fp, code, msg, headers): # pylint:disable=unused-argument
383 host = req.get_host() 383 host = req.get_host()
384 retry = self.http_error_auth_reqed('www-authenticate', host, req, headers) 384 retry = self.http_error_auth_reqed('www-authenticate', host, req, headers)
385 return retry 385 return retry
diff --git a/manifest_xml.py b/manifest_xml.py
index 3ac607ec..295493de 100644
--- a/manifest_xml.py
+++ b/manifest_xml.py
@@ -102,7 +102,10 @@ class _XmlRemote(object):
102 remoteName = self.name 102 remoteName = self.name
103 if self.remoteAlias: 103 if self.remoteAlias:
104 remoteName = self.remoteAlias 104 remoteName = self.remoteAlias
105 return RemoteSpec(remoteName, url, self.reviewUrl) 105 return RemoteSpec(remoteName,
106 url=url,
107 review=self.reviewUrl,
108 orig_name=self.name)
106 109
107class XmlManifest(object): 110class XmlManifest(object):
108 """manages the repo configuration file""" 111 """manages the repo configuration file"""
@@ -249,9 +252,9 @@ class XmlManifest(object):
249 e.setAttribute('path', relpath) 252 e.setAttribute('path', relpath)
250 remoteName = None 253 remoteName = None
251 if d.remote: 254 if d.remote:
252 remoteName = d.remote.remoteAlias or d.remote.name 255 remoteName = d.remote.name
253 if not d.remote or p.remote.name != remoteName: 256 if not d.remote or p.remote.orig_name != remoteName:
254 remoteName = p.remote.name 257 remoteName = p.remote.orig_name
255 e.setAttribute('remote', remoteName) 258 e.setAttribute('remote', remoteName)
256 if peg_rev: 259 if peg_rev:
257 if self.IsMirror: 260 if self.IsMirror:
@@ -267,7 +270,7 @@ class XmlManifest(object):
267 # isn't our value 270 # isn't our value
268 e.setAttribute('upstream', p.revisionExpr) 271 e.setAttribute('upstream', p.revisionExpr)
269 else: 272 else:
270 revision = self.remotes[remoteName].revision or d.revisionExpr 273 revision = self.remotes[p.remote.orig_name].revision or d.revisionExpr
271 if not revision or revision != p.revisionExpr: 274 if not revision or revision != p.revisionExpr:
272 e.setAttribute('revision', p.revisionExpr) 275 e.setAttribute('revision', p.revisionExpr)
273 if p.upstream and p.upstream != p.revisionExpr: 276 if p.upstream and p.upstream != p.revisionExpr:
@@ -969,5 +972,5 @@ class GitcManifest(XmlManifest):
969 def _output_manifest_project_extras(self, p, e): 972 def _output_manifest_project_extras(self, p, e):
970 """Output GITC Specific Project attributes""" 973 """Output GITC Specific Project attributes"""
971 if p.old_revision: 974 if p.old_revision:
972 e.setAttribute('old-revision', str(p.old_revision)) 975 e.setAttribute('old-revision', str(p.old_revision))
973 976
diff --git a/project.py b/project.py
index 6dfb31c3..c91085c3 100644
--- a/project.py
+++ b/project.py
@@ -30,7 +30,8 @@ import traceback
30 30
31from color import Coloring 31from color import Coloring
32from git_command import GitCommand, git_require 32from git_command import GitCommand, git_require
33from git_config import GitConfig, IsId, GetSchemeFromUrl, GetUrlCookieFile, ID_RE 33from git_config import GitConfig, IsId, GetSchemeFromUrl, GetUrlCookieFile, \
34 ID_RE
34from error import GitError, HookError, UploadError, DownloadError 35from error import GitError, HookError, UploadError, DownloadError
35from error import ManifestInvalidRevisionError 36from error import ManifestInvalidRevisionError
36from error import NoManifestException 37from error import NoManifestException
@@ -44,6 +45,7 @@ if not is_python3():
44 input = raw_input 45 input = raw_input
45 # pylint:enable=W0622 46 # pylint:enable=W0622
46 47
48
47def _lwrite(path, content): 49def _lwrite(path, content):
48 lock = '%s.lock' % path 50 lock = '%s.lock' % path
49 51
@@ -59,21 +61,27 @@ def _lwrite(path, content):
59 os.remove(lock) 61 os.remove(lock)
60 raise 62 raise
61 63
64
62def _error(fmt, *args): 65def _error(fmt, *args):
63 msg = fmt % args 66 msg = fmt % args
64 print('error: %s' % msg, file=sys.stderr) 67 print('error: %s' % msg, file=sys.stderr)
65 68
69
66def _warn(fmt, *args): 70def _warn(fmt, *args):
67 msg = fmt % args 71 msg = fmt % args
68 print('warn: %s' % msg, file=sys.stderr) 72 print('warn: %s' % msg, file=sys.stderr)
69 73
74
70def not_rev(r): 75def not_rev(r):
71 return '^' + r 76 return '^' + r
72 77
78
73def sq(r): 79def sq(r):
74 return "'" + r.replace("'", "'\''") + "'" 80 return "'" + r.replace("'", "'\''") + "'"
75 81
76_project_hook_list = None 82_project_hook_list = None
83
84
77def _ProjectHooks(): 85def _ProjectHooks():
78 """List the hooks present in the 'hooks' directory. 86 """List the hooks present in the 'hooks' directory.
79 87
@@ -107,15 +115,14 @@ class DownloadedChange(object):
107 @property 115 @property
108 def commits(self): 116 def commits(self):
109 if self._commit_cache is None: 117 if self._commit_cache is None:
110 self._commit_cache = self.project.bare_git.rev_list( 118 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
111 '--abbrev=8', 119 '--abbrev-commit',
112 '--abbrev-commit', 120 '--pretty=oneline',
113 '--pretty=oneline', 121 '--reverse',
114 '--reverse', 122 '--date-order',
115 '--date-order', 123 not_rev(self.base),
116 not_rev(self.base), 124 self.commit,
117 self.commit, 125 '--')
118 '--')
119 return self._commit_cache 126 return self._commit_cache
120 127
121 128
@@ -134,36 +141,36 @@ class ReviewableBranch(object):
134 @property 141 @property
135 def commits(self): 142 def commits(self):
136 if self._commit_cache is None: 143 if self._commit_cache is None:
137 self._commit_cache = self.project.bare_git.rev_list( 144 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
138 '--abbrev=8', 145 '--abbrev-commit',
139 '--abbrev-commit', 146 '--pretty=oneline',
140 '--pretty=oneline', 147 '--reverse',
141 '--reverse', 148 '--date-order',
142 '--date-order', 149 not_rev(self.base),
143 not_rev(self.base), 150 R_HEADS + self.name,
144 R_HEADS + self.name, 151 '--')
145 '--')
146 return self._commit_cache 152 return self._commit_cache
147 153
148 @property 154 @property
149 def unabbrev_commits(self): 155 def unabbrev_commits(self):
150 r = dict() 156 r = dict()
151 for commit in self.project.bare_git.rev_list( 157 for commit in self.project.bare_git.rev_list(not_rev(self.base),
152 not_rev(self.base), 158 R_HEADS + self.name,
153 R_HEADS + self.name, 159 '--'):
154 '--'):
155 r[commit[0:8]] = commit 160 r[commit[0:8]] = commit
156 return r 161 return r
157 162
158 @property 163 @property
159 def date(self): 164 def date(self):
160 return self.project.bare_git.log( 165 return self.project.bare_git.log('--pretty=format:%cd',
161 '--pretty=format:%cd', 166 '-n', '1',
162 '-n', '1', 167 R_HEADS + self.name,
163 R_HEADS + self.name, 168 '--')
164 '--')
165 169
166 def UploadForReview(self, people, auto_topic=False, draft=False, dest_branch=None): 170 def UploadForReview(self, people,
171 auto_topic=False,
172 draft=False,
173 dest_branch=None):
167 self.project.UploadForReview(self.name, 174 self.project.UploadForReview(self.name,
168 people, 175 people,
169 auto_topic=auto_topic, 176 auto_topic=auto_topic,
@@ -173,8 +180,8 @@ class ReviewableBranch(object):
173 def GetPublishedRefs(self): 180 def GetPublishedRefs(self):
174 refs = {} 181 refs = {}
175 output = self.project.bare_git.ls_remote( 182 output = self.project.bare_git.ls_remote(
176 self.branch.remote.SshReviewUrl(self.project.UserEmail), 183 self.branch.remote.SshReviewUrl(self.project.UserEmail),
177 'refs/changes/*') 184 'refs/changes/*')
178 for line in output.split('\n'): 185 for line in output.split('\n'):
179 try: 186 try:
180 (sha, ref) = line.split() 187 (sha, ref) = line.split()
@@ -184,7 +191,9 @@ class ReviewableBranch(object):
184 191
185 return refs 192 return refs
186 193
194
187class StatusColoring(Coloring): 195class StatusColoring(Coloring):
196
188 def __init__(self, config): 197 def __init__(self, config):
189 Coloring.__init__(self, config, 'status') 198 Coloring.__init__(self, config, 'status')
190 self.project = self.printer('header', attr='bold') 199 self.project = self.printer('header', attr='bold')
@@ -198,17 +207,22 @@ class StatusColoring(Coloring):
198 207
199 208
200class DiffColoring(Coloring): 209class DiffColoring(Coloring):
210
201 def __init__(self, config): 211 def __init__(self, config):
202 Coloring.__init__(self, config, 'diff') 212 Coloring.__init__(self, config, 'diff')
203 self.project = self.printer('header', attr='bold') 213 self.project = self.printer('header', attr='bold')
204 214
215
205class _Annotation(object): 216class _Annotation(object):
217
206 def __init__(self, name, value, keep): 218 def __init__(self, name, value, keep):
207 self.name = name 219 self.name = name
208 self.value = value 220 self.value = value
209 self.keep = keep 221 self.keep = keep
210 222
223
211class _CopyFile(object): 224class _CopyFile(object):
225
212 def __init__(self, src, dest, abssrc, absdest): 226 def __init__(self, src, dest, abssrc, absdest):
213 self.src = src 227 self.src = src
214 self.dest = dest 228 self.dest = dest
@@ -236,7 +250,9 @@ class _CopyFile(object):
236 except IOError: 250 except IOError:
237 _error('Cannot copy file %s to %s', src, dest) 251 _error('Cannot copy file %s to %s', src, dest)
238 252
253
239class _LinkFile(object): 254class _LinkFile(object):
255
240 def __init__(self, git_worktree, src, dest, relsrc, absdest): 256 def __init__(self, git_worktree, src, dest, relsrc, absdest):
241 self.git_worktree = git_worktree 257 self.git_worktree = git_worktree
242 self.src = src 258 self.src = src
@@ -275,7 +291,7 @@ class _LinkFile(object):
275 absDestDir = self.abs_dest 291 absDestDir = self.abs_dest
276 if os.path.exists(absDestDir) and not os.path.isdir(absDestDir): 292 if os.path.exists(absDestDir) and not os.path.isdir(absDestDir):
277 _error('Link error: src with wildcard, %s must be a directory', 293 _error('Link error: src with wildcard, %s must be a directory',
278 absDestDir) 294 absDestDir)
279 else: 295 else:
280 absSrcFiles = glob.glob(absSrc) 296 absSrcFiles = glob.glob(absSrc)
281 for absSrcFile in absSrcFiles: 297 for absSrcFile in absSrcFiles:
@@ -292,18 +308,24 @@ class _LinkFile(object):
292 relSrc = os.path.join(relSrcDir, srcFile) 308 relSrc = os.path.join(relSrcDir, srcFile)
293 self.__linkIt(relSrc, absDest) 309 self.__linkIt(relSrc, absDest)
294 310
311
295class RemoteSpec(object): 312class RemoteSpec(object):
313
296 def __init__(self, 314 def __init__(self,
297 name, 315 name,
298 url=None, 316 url=None,
299 review=None, 317 review=None,
300 revision=None): 318 revision=None,
319 orig_name=None):
301 self.name = name 320 self.name = name
302 self.url = url 321 self.url = url
303 self.review = review 322 self.review = review
304 self.revision = revision 323 self.revision = revision
324 self.orig_name = orig_name
325
305 326
306class RepoHook(object): 327class RepoHook(object):
328
307 """A RepoHook contains information about a script to run as a hook. 329 """A RepoHook contains information about a script to run as a hook.
308 330
309 Hooks are used to run a python script before running an upload (for instance, 331 Hooks are used to run a python script before running an upload (for instance,
@@ -316,6 +338,7 @@ class RepoHook(object):
316 Hooks are always python. When a hook is run, we will load the hook into the 338 Hooks are always python. When a hook is run, we will load the hook into the
317 interpreter and execute its main() function. 339 interpreter and execute its main() function.
318 """ 340 """
341
319 def __init__(self, 342 def __init__(self,
320 hook_type, 343 hook_type,
321 hooks_project, 344 hooks_project,
@@ -430,8 +453,8 @@ class RepoHook(object):
430 ' %s\n' 453 ' %s\n'
431 '\n' 454 '\n'
432 'Do you want to allow this script to run ' 455 'Do you want to allow this script to run '
433 '(yes/yes-never-ask-again/NO)? ') % ( 456 '(yes/yes-never-ask-again/NO)? ') % (self._GetMustVerb(),
434 self._GetMustVerb(), self._script_fullpath) 457 self._script_fullpath)
435 response = input(prompt).lower() 458 response = input(prompt).lower()
436 print() 459 print()
437 460
@@ -475,19 +498,18 @@ class RepoHook(object):
475 498
476 # Exec, storing global context in the context dict. We catch exceptions 499 # Exec, storing global context in the context dict. We catch exceptions
477 # and convert to a HookError w/ just the failing traceback. 500 # and convert to a HookError w/ just the failing traceback.
478 context = {} 501 context = {'__file__': self._script_fullpath}
479 try: 502 try:
480 exec(compile(open(self._script_fullpath).read(), 503 exec(compile(open(self._script_fullpath).read(),
481 self._script_fullpath, 'exec'), context) 504 self._script_fullpath, 'exec'), context)
482 except Exception: 505 except Exception:
483 raise HookError('%s\nFailed to import %s hook; see traceback above.' % ( 506 raise HookError('%s\nFailed to import %s hook; see traceback above.' %
484 traceback.format_exc(), self._hook_type)) 507 (traceback.format_exc(), self._hook_type))
485 508
486 # Running the script should have defined a main() function. 509 # Running the script should have defined a main() function.
487 if 'main' not in context: 510 if 'main' not in context:
488 raise HookError('Missing main() in: "%s"' % self._script_fullpath) 511 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
489 512
490
491 # Add 'hook_should_take_kwargs' to the arguments to be passed to main. 513 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
492 # We don't actually want hooks to define their main with this argument-- 514 # We don't actually want hooks to define their main with this argument--
493 # it's there to remind them that their hook should always take **kwargs. 515 # it's there to remind them that their hook should always take **kwargs.
@@ -505,8 +527,8 @@ class RepoHook(object):
505 context['main'](**kwargs) 527 context['main'](**kwargs)
506 except Exception: 528 except Exception:
507 raise HookError('%s\nFailed to run main() for %s hook; see traceback ' 529 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
508 'above.' % ( 530 'above.' % (traceback.format_exc(),
509 traceback.format_exc(), self._hook_type)) 531 self._hook_type))
510 finally: 532 finally:
511 # Restore sys.path and CWD. 533 # Restore sys.path and CWD.
512 sys.path = orig_syspath 534 sys.path = orig_syspath
@@ -530,8 +552,8 @@ class RepoHook(object):
530 to run a required hook (from _CheckForHookApproval). 552 to run a required hook (from _CheckForHookApproval).
531 """ 553 """
532 # No-op if there is no hooks project or if hook is disabled. 554 # No-op if there is no hooks project or if hook is disabled.
533 if ((not self._hooks_project) or 555 if ((not self._hooks_project) or (self._hook_type not in
534 (self._hook_type not in self._hooks_project.enabled_repo_hooks)): 556 self._hooks_project.enabled_repo_hooks)):
535 return 557 return
536 558
537 # Bail with a nice error if we can't find the hook. 559 # Bail with a nice error if we can't find the hook.
@@ -553,6 +575,7 @@ class Project(object):
553 # These objects can only be used by a single working tree. 575 # These objects can only be used by a single working tree.
554 working_tree_files = ['config', 'packed-refs', 'shallow'] 576 working_tree_files = ['config', 'packed-refs', 'shallow']
555 working_tree_dirs = ['logs', 'refs'] 577 working_tree_dirs = ['logs', 'refs']
578
556 def __init__(self, 579 def __init__(self,
557 manifest, 580 manifest,
558 name, 581 name,
@@ -611,9 +634,9 @@ class Project(object):
611 self.relpath = relpath 634 self.relpath = relpath
612 self.revisionExpr = revisionExpr 635 self.revisionExpr = revisionExpr
613 636
614 if revisionId is None \ 637 if revisionId is None \
615 and revisionExpr \ 638 and revisionExpr \
616 and IsId(revisionExpr): 639 and IsId(revisionExpr):
617 self.revisionId = revisionExpr 640 self.revisionId = revisionExpr
618 else: 641 else:
619 self.revisionId = revisionId 642 self.revisionId = revisionId
@@ -633,9 +656,8 @@ class Project(object):
633 self.copyfiles = [] 656 self.copyfiles = []
634 self.linkfiles = [] 657 self.linkfiles = []
635 self.annotations = [] 658 self.annotations = []
636 self.config = GitConfig.ForRepository( 659 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
637 gitdir=self.gitdir, 660 defaults=self.manifest.globalConfig)
638 defaults=self.manifest.globalConfig)
639 661
640 if self.worktree: 662 if self.worktree:
641 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir) 663 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
@@ -773,7 +795,7 @@ class Project(object):
773 """ 795 """
774 expanded_manifest_groups = manifest_groups or ['default'] 796 expanded_manifest_groups = manifest_groups or ['default']
775 expanded_project_groups = ['all'] + (self.groups or []) 797 expanded_project_groups = ['all'] + (self.groups or [])
776 if not 'notdefault' in expanded_project_groups: 798 if 'notdefault' not in expanded_project_groups:
777 expanded_project_groups += ['default'] 799 expanded_project_groups += ['default']
778 800
779 matched = False 801 matched = False
@@ -785,7 +807,7 @@ class Project(object):
785 807
786 return matched 808 return matched
787 809
788## Status Display ## 810# Status Display ##
789 def UncommitedFiles(self, get_all=True): 811 def UncommitedFiles(self, get_all=True):
790 """Returns a list of strings, uncommitted files in the git tree. 812 """Returns a list of strings, uncommitted files in the git tree.
791 813
@@ -837,7 +859,7 @@ class Project(object):
837 output: If specified, redirect the output to this object. 859 output: If specified, redirect the output to this object.
838 """ 860 """
839 if not os.path.isdir(self.worktree): 861 if not os.path.isdir(self.worktree):
840 if output_redir == None: 862 if output_redir is None:
841 output_redir = sys.stdout 863 output_redir = sys.stdout
842 print(file=output_redir) 864 print(file=output_redir)
843 print('project %s/' % self.relpath, file=output_redir) 865 print('project %s/' % self.relpath, file=output_redir)
@@ -856,7 +878,7 @@ class Project(object):
856 return 'CLEAN' 878 return 'CLEAN'
857 879
858 out = StatusColoring(self.config) 880 out = StatusColoring(self.config)
859 if not output_redir == None: 881 if output_redir is not None:
860 out.redirect(output_redir) 882 out.redirect(output_redir)
861 out.project('project %-40s', self.relpath + '/ ') 883 out.project('project %-40s', self.relpath + '/ ')
862 884
@@ -899,7 +921,7 @@ class Project(object):
899 921
900 if i and i.src_path: 922 if i and i.src_path:
901 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status, 923 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
902 i.src_path, p, i.level) 924 i.src_path, p, i.level)
903 else: 925 else:
904 line = ' %s%s\t%s' % (i_status, f_status, p) 926 line = ' %s%s\t%s' % (i_status, f_status, p)
905 927
@@ -942,7 +964,7 @@ class Project(object):
942 p.Wait() 964 p.Wait()
943 965
944 966
945## Publish / Upload ## 967# Publish / Upload ##
946 968
947 def WasPublished(self, branch, all_refs=None): 969 def WasPublished(self, branch, all_refs=None):
948 """Was the branch published (uploaded) for code review? 970 """Was the branch published (uploaded) for code review?
@@ -1085,7 +1107,7 @@ class Project(object):
1085 message=msg) 1107 message=msg)
1086 1108
1087 1109
1088## Sync ## 1110# Sync ##
1089 1111
1090 def _ExtractArchive(self, tarpath, path=None): 1112 def _ExtractArchive(self, tarpath, path=None):
1091 """Extract the given tar on its current location 1113 """Extract the given tar on its current location
@@ -1103,15 +1125,15 @@ class Project(object):
1103 return False 1125 return False
1104 1126
1105 def Sync_NetworkHalf(self, 1127 def Sync_NetworkHalf(self,
1106 quiet=False, 1128 quiet=False,
1107 is_new=None, 1129 is_new=None,
1108 current_branch_only=False, 1130 current_branch_only=False,
1109 force_sync=False, 1131 force_sync=False,
1110 clone_bundle=True, 1132 clone_bundle=True,
1111 no_tags=False, 1133 no_tags=False,
1112 archive=False, 1134 archive=False,
1113 optimized_fetch=False, 1135 optimized_fetch=False,
1114 prune=False): 1136 prune=False):
1115 """Perform only the network IO portion of the sync process. 1137 """Perform only the network IO portion of the sync process.
1116 Local working directory/branch state is not affected. 1138 Local working directory/branch state is not affected.
1117 """ 1139 """
@@ -1164,8 +1186,8 @@ class Project(object):
1164 alt_dir = None 1186 alt_dir = None
1165 1187
1166 if clone_bundle \ 1188 if clone_bundle \
1167 and alt_dir is None \ 1189 and alt_dir is None \
1168 and self._ApplyCloneBundle(initial=is_new, quiet=quiet): 1190 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
1169 is_new = False 1191 is_new = False
1170 1192
1171 if not current_branch_only: 1193 if not current_branch_only:
@@ -1177,12 +1199,13 @@ class Project(object):
1177 elif self.manifest.default.sync_c: 1199 elif self.manifest.default.sync_c:
1178 current_branch_only = True 1200 current_branch_only = True
1179 1201
1180 need_to_fetch = not (optimized_fetch and \ 1202 need_to_fetch = not (optimized_fetch and
1181 (ID_RE.match(self.revisionExpr) and self._CheckForSha1())) 1203 (ID_RE.match(self.revisionExpr) and
1182 if (need_to_fetch 1204 self._CheckForSha1()))
1183 and not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir, 1205 if (need_to_fetch and
1184 current_branch_only=current_branch_only, 1206 not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1185 no_tags=no_tags, prune=prune)): 1207 current_branch_only=current_branch_only,
1208 no_tags=no_tags, prune=prune)):
1186 return False 1209 return False
1187 1210
1188 if self.worktree: 1211 if self.worktree:
@@ -1219,9 +1242,8 @@ class Project(object):
1219 try: 1242 try:
1220 return self.bare_git.rev_list(self.revisionExpr, '-1')[0] 1243 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1221 except GitError: 1244 except GitError:
1222 raise ManifestInvalidRevisionError( 1245 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1223 'revision %s in %s not found' % (self.revisionExpr, 1246 (self.revisionExpr, self.name))
1224 self.name))
1225 1247
1226 def GetRevisionId(self, all_refs=None): 1248 def GetRevisionId(self, all_refs=None):
1227 if self.revisionId: 1249 if self.revisionId:
@@ -1236,9 +1258,8 @@ class Project(object):
1236 try: 1258 try:
1237 return self.bare_git.rev_parse('--verify', '%s^0' % rev) 1259 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1238 except GitError: 1260 except GitError:
1239 raise ManifestInvalidRevisionError( 1261 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1240 'revision %s in %s not found' % (self.revisionExpr, 1262 (self.revisionExpr, self.name))
1241 self.name))
1242 1263
1243 def Sync_LocalHalf(self, syncbuf, force_sync=False): 1264 def Sync_LocalHalf(self, syncbuf, force_sync=False):
1244 """Perform only the local IO portion of the sync process. 1265 """Perform only the local IO portion of the sync process.
@@ -1327,8 +1348,8 @@ class Project(object):
1327 # to rewrite the published commits so we punt. 1348 # to rewrite the published commits so we punt.
1328 # 1349 #
1329 syncbuf.fail(self, 1350 syncbuf.fail(self,
1330 "branch %s is published (but not merged) and is now %d commits behind" 1351 "branch %s is published (but not merged) and is now "
1331 % (branch.name, len(upstream_gain))) 1352 "%d commits behind" % (branch.name, len(upstream_gain)))
1332 return 1353 return
1333 elif pub == head: 1354 elif pub == head:
1334 # All published commits are merged, and thus we are a 1355 # All published commits are merged, and thus we are a
@@ -1422,7 +1443,7 @@ class Project(object):
1422 remote = self.GetRemote(self.remote.name) 1443 remote = self.GetRemote(self.remote.name)
1423 1444
1424 cmd = ['fetch', remote.name] 1445 cmd = ['fetch', remote.name]
1425 cmd.append('refs/changes/%2.2d/%d/%d' \ 1446 cmd.append('refs/changes/%2.2d/%d/%d'
1426 % (change_id % 100, change_id, patch_id)) 1447 % (change_id % 100, change_id, patch_id))
1427 if GitCommand(self, cmd, bare=True).Wait() != 0: 1448 if GitCommand(self, cmd, bare=True).Wait() != 0:
1428 return None 1449 return None
@@ -1433,7 +1454,7 @@ class Project(object):
1433 self.bare_git.rev_parse('FETCH_HEAD')) 1454 self.bare_git.rev_parse('FETCH_HEAD'))
1434 1455
1435 1456
1436## Branch Management ## 1457# Branch Management ##
1437 1458
1438 def StartBranch(self, name, branch_merge=''): 1459 def StartBranch(self, name, branch_merge=''):
1439 """Create a new branch off the manifest's revision. 1460 """Create a new branch off the manifest's revision.
@@ -1620,10 +1641,11 @@ class Project(object):
1620 return kept 1641 return kept
1621 1642
1622 1643
1623## Submodule Management ## 1644# Submodule Management ##
1624 1645
1625 def GetRegisteredSubprojects(self): 1646 def GetRegisteredSubprojects(self):
1626 result = [] 1647 result = []
1648
1627 def rec(subprojects): 1649 def rec(subprojects):
1628 if not subprojects: 1650 if not subprojects:
1629 return 1651 return
@@ -1658,6 +1680,7 @@ class Project(object):
1658 1680
1659 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$') 1681 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1660 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$') 1682 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
1683
1661 def parse_gitmodules(gitdir, rev): 1684 def parse_gitmodules(gitdir, rev):
1662 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev] 1685 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1663 try: 1686 try:
@@ -1767,7 +1790,7 @@ class Project(object):
1767 return result 1790 return result
1768 1791
1769 1792
1770## Direct Git Commands ## 1793# Direct Git Commands ##
1771 def _CheckForSha1(self): 1794 def _CheckForSha1(self):
1772 try: 1795 try:
1773 # if revision (sha or tag) is not present then following function 1796 # if revision (sha or tag) is not present then following function
@@ -1791,7 +1814,6 @@ class Project(object):
1791 if command.Wait() != 0: 1814 if command.Wait() != 0:
1792 raise GitError('git archive %s: %s' % (self.name, command.stderr)) 1815 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1793 1816
1794
1795 def _RemoteFetch(self, name=None, 1817 def _RemoteFetch(self, name=None,
1796 current_branch_only=False, 1818 current_branch_only=False,
1797 initial=False, 1819 initial=False,
@@ -1958,9 +1980,9 @@ class Project(object):
1958 break 1980 break
1959 continue 1981 continue
1960 elif current_branch_only and is_sha1 and ret == 128: 1982 elif current_branch_only and is_sha1 and ret == 128:
1961 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1 1983 # Exit code 128 means "couldn't find the ref you asked for"; if we're
1962 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus 1984 # in sha1 mode, we just tried sync'ing from the upstream field; it
1963 # abort the optimization attempt and do a full sync. 1985 # doesn't exist, thus abort the optimization attempt and do a full sync.
1964 break 1986 break
1965 elif ret < 0: 1987 elif ret < 0:
1966 # Git died with a signal, exit immediately 1988 # Git died with a signal, exit immediately
@@ -1987,20 +2009,24 @@ class Project(object):
1987 initial=False, quiet=quiet, alt_dir=alt_dir) 2009 initial=False, quiet=quiet, alt_dir=alt_dir)
1988 if self.clone_depth: 2010 if self.clone_depth:
1989 self.clone_depth = None 2011 self.clone_depth = None
1990 return self._RemoteFetch(name=name, current_branch_only=current_branch_only, 2012 return self._RemoteFetch(name=name,
2013 current_branch_only=current_branch_only,
1991 initial=False, quiet=quiet, alt_dir=alt_dir) 2014 initial=False, quiet=quiet, alt_dir=alt_dir)
1992 2015
1993 return ok 2016 return ok
1994 2017
1995 def _ApplyCloneBundle(self, initial=False, quiet=False): 2018 def _ApplyCloneBundle(self, initial=False, quiet=False):
1996 if initial and (self.manifest.manifestProject.config.GetString('repo.depth') or self.clone_depth): 2019 if initial and \
2020 (self.manifest.manifestProject.config.GetString('repo.depth') or
2021 self.clone_depth):
1997 return False 2022 return False
1998 2023
1999 remote = self.GetRemote(self.remote.name) 2024 remote = self.GetRemote(self.remote.name)
2000 bundle_url = remote.url + '/clone.bundle' 2025 bundle_url = remote.url + '/clone.bundle'
2001 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url) 2026 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
2002 if GetSchemeFromUrl(bundle_url) not in ( 2027 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2003 'http', 'https', 'persistent-http', 'persistent-https'): 2028 'persistent-http',
2029 'persistent-https'):
2004 return False 2030 return False
2005 2031
2006 bundle_dst = os.path.join(self.gitdir, 'clone.bundle') 2032 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
@@ -2049,7 +2075,7 @@ class Project(object):
2049 os.remove(tmpPath) 2075 os.remove(tmpPath)
2050 if 'http_proxy' in os.environ and 'darwin' == sys.platform: 2076 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
2051 cmd += ['--proxy', os.environ['http_proxy']] 2077 cmd += ['--proxy', os.environ['http_proxy']]
2052 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy): 2078 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, _proxy):
2053 if cookiefile: 2079 if cookiefile:
2054 cmd += ['--cookie', cookiefile, '--cookie-jar', cookiefile] 2080 cmd += ['--cookie', cookiefile, '--cookie-jar', cookiefile]
2055 if srcUrl.startswith('persistent-'): 2081 if srcUrl.startswith('persistent-'):
@@ -2168,11 +2194,12 @@ class Project(object):
2168 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False) 2194 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2169 except GitError as e: 2195 except GitError as e:
2170 if force_sync: 2196 if force_sync:
2171 print("Retrying clone after deleting %s" % self.gitdir, file=sys.stderr) 2197 print("Retrying clone after deleting %s" %
2198 self.gitdir, file=sys.stderr)
2172 try: 2199 try:
2173 shutil.rmtree(os.path.realpath(self.gitdir)) 2200 shutil.rmtree(os.path.realpath(self.gitdir))
2174 if self.worktree and os.path.exists( 2201 if self.worktree and os.path.exists(os.path.realpath
2175 os.path.realpath(self.worktree)): 2202 (self.worktree)):
2176 shutil.rmtree(os.path.realpath(self.worktree)) 2203 shutil.rmtree(os.path.realpath(self.worktree))
2177 return self._InitGitDir(mirror_git=mirror_git, force_sync=False) 2204 return self._InitGitDir(mirror_git=mirror_git, force_sync=False)
2178 except: 2205 except:
@@ -2231,7 +2258,7 @@ class Project(object):
2231 name = os.path.basename(stock_hook) 2258 name = os.path.basename(stock_hook)
2232 2259
2233 if name in ('commit-msg',) and not self.remote.review \ 2260 if name in ('commit-msg',) and not self.remote.review \
2234 and not self is self.manifest.manifestProject: 2261 and self is not self.manifest.manifestProject:
2235 # Don't install a Gerrit Code Review hook if this 2262 # Don't install a Gerrit Code Review hook if this
2236 # project does not appear to use it for reviews. 2263 # project does not appear to use it for reviews.
2237 # 2264 #
@@ -2246,7 +2273,8 @@ class Project(object):
2246 if filecmp.cmp(stock_hook, dst, shallow=False): 2273 if filecmp.cmp(stock_hook, dst, shallow=False):
2247 os.remove(dst) 2274 os.remove(dst)
2248 else: 2275 else:
2249 _warn("%s: Not replacing locally modified %s hook", self.relpath, name) 2276 _warn("%s: Not replacing locally modified %s hook",
2277 self.relpath, name)
2250 continue 2278 continue
2251 try: 2279 try:
2252 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst) 2280 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
@@ -2292,8 +2320,8 @@ class Project(object):
2292 self.bare_git.symbolic_ref('-m', msg, ref, dst) 2320 self.bare_git.symbolic_ref('-m', msg, ref, dst)
2293 2321
2294 def _CheckDirReference(self, srcdir, destdir, share_refs): 2322 def _CheckDirReference(self, srcdir, destdir, share_refs):
2295 symlink_files = self.shareable_files 2323 symlink_files = self.shareable_files[:]
2296 symlink_dirs = self.shareable_dirs 2324 symlink_dirs = self.shareable_dirs[:]
2297 if share_refs: 2325 if share_refs:
2298 symlink_files += self.working_tree_files 2326 symlink_files += self.working_tree_files
2299 symlink_dirs += self.working_tree_dirs 2327 symlink_dirs += self.working_tree_dirs
@@ -2321,8 +2349,8 @@ class Project(object):
2321 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|. 2349 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2322 This saves you the effort of initializing |dotgit| yourself. 2350 This saves you the effort of initializing |dotgit| yourself.
2323 """ 2351 """
2324 symlink_files = self.shareable_files 2352 symlink_files = self.shareable_files[:]
2325 symlink_dirs = self.shareable_dirs 2353 symlink_dirs = self.shareable_dirs[:]
2326 if share_refs: 2354 if share_refs:
2327 symlink_files += self.working_tree_files 2355 symlink_files += self.working_tree_files
2328 symlink_dirs += self.working_tree_dirs 2356 symlink_dirs += self.working_tree_dirs
@@ -2414,7 +2442,7 @@ class Project(object):
2414 def _allrefs(self): 2442 def _allrefs(self):
2415 return self.bare_ref.all 2443 return self.bare_ref.all
2416 2444
2417 def _getLogs(self, rev1, rev2, oneline=False, color=True): 2445 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
2418 """Get logs between two revisions of this project.""" 2446 """Get logs between two revisions of this project."""
2419 comp = '..' 2447 comp = '..'
2420 if rev1: 2448 if rev1:
@@ -2425,6 +2453,8 @@ class Project(object):
2425 out = DiffColoring(self.config) 2453 out = DiffColoring(self.config)
2426 if out.is_on and color: 2454 if out.is_on and color:
2427 cmd.append('--color') 2455 cmd.append('--color')
2456 if pretty_format is not None:
2457 cmd.append('--pretty=format:%s' % pretty_format)
2428 if oneline: 2458 if oneline:
2429 cmd.append('--oneline') 2459 cmd.append('--oneline')
2430 2460
@@ -2441,17 +2471,21 @@ class Project(object):
2441 raise 2471 raise
2442 return None 2472 return None
2443 2473
2444 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True): 2474 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2475 pretty_format=None):
2445 """Get the list of logs from this revision to given revisionId""" 2476 """Get the list of logs from this revision to given revisionId"""
2446 logs = {} 2477 logs = {}
2447 selfId = self.GetRevisionId(self._allrefs) 2478 selfId = self.GetRevisionId(self._allrefs)
2448 toId = toProject.GetRevisionId(toProject._allrefs) 2479 toId = toProject.GetRevisionId(toProject._allrefs)
2449 2480
2450 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color) 2481 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
2451 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color) 2482 pretty_format=pretty_format)
2483 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
2484 pretty_format=pretty_format)
2452 return logs 2485 return logs
2453 2486
2454 class _GitGetByExec(object): 2487 class _GitGetByExec(object):
2488
2455 def __init__(self, project, bare, gitdir): 2489 def __init__(self, project, bare, gitdir):
2456 self._project = project 2490 self._project = project
2457 self._bare = bare 2491 self._bare = bare
@@ -2470,8 +2504,8 @@ class Project(object):
2470 if p.Wait() == 0: 2504 if p.Wait() == 0:
2471 out = p.stdout 2505 out = p.stdout
2472 if out: 2506 if out:
2507 # Backslash is not anomalous
2473 return out[:-1].split('\0') # pylint: disable=W1401 2508 return out[:-1].split('\0') # pylint: disable=W1401
2474 # Backslash is not anomalous
2475 return [] 2509 return []
2476 2510
2477 def DiffZ(self, name, *args): 2511 def DiffZ(self, name, *args):
@@ -2497,6 +2531,7 @@ class Project(object):
2497 break 2531 break
2498 2532
2499 class _Info(object): 2533 class _Info(object):
2534
2500 def __init__(self, path, omode, nmode, oid, nid, state): 2535 def __init__(self, path, omode, nmode, oid, nid, state):
2501 self.path = path 2536 self.path = path
2502 self.src_path = None 2537 self.src_path = None
@@ -2599,10 +2634,8 @@ class Project(object):
2599 line = line[:-1] 2634 line = line[:-1]
2600 r.append(line) 2635 r.append(line)
2601 if p.Wait() != 0: 2636 if p.Wait() != 0:
2602 raise GitError('%s rev-list %s: %s' % ( 2637 raise GitError('%s rev-list %s: %s' %
2603 self._project.name, 2638 (self._project.name, str(args), p.stderr))
2604 str(args),
2605 p.stderr))
2606 return r 2639 return r
2607 2640
2608 def __getattr__(self, name): 2641 def __getattr__(self, name):
@@ -2625,6 +2658,7 @@ class Project(object):
2625 A callable object that will try to call git with the named command. 2658 A callable object that will try to call git with the named command.
2626 """ 2659 """
2627 name = name.replace('_', '-') 2660 name = name.replace('_', '-')
2661
2628 def runner(*args, **kwargs): 2662 def runner(*args, **kwargs):
2629 cmdv = [] 2663 cmdv = []
2630 config = kwargs.pop('config', None) 2664 config = kwargs.pop('config', None)
@@ -2647,10 +2681,8 @@ class Project(object):
2647 capture_stdout=True, 2681 capture_stdout=True,
2648 capture_stderr=True) 2682 capture_stderr=True)
2649 if p.Wait() != 0: 2683 if p.Wait() != 0:
2650 raise GitError('%s %s: %s' % ( 2684 raise GitError('%s %s: %s' %
2651 self._project.name, 2685 (self._project.name, name, p.stderr))
2652 name,
2653 p.stderr))
2654 r = p.stdout 2686 r = p.stdout
2655 try: 2687 try:
2656 r = r.decode('utf-8') 2688 r = r.decode('utf-8')
@@ -2663,14 +2695,19 @@ class Project(object):
2663 2695
2664 2696
2665class _PriorSyncFailedError(Exception): 2697class _PriorSyncFailedError(Exception):
2698
2666 def __str__(self): 2699 def __str__(self):
2667 return 'prior sync failed; rebase still in progress' 2700 return 'prior sync failed; rebase still in progress'
2668 2701
2702
2669class _DirtyError(Exception): 2703class _DirtyError(Exception):
2704
2670 def __str__(self): 2705 def __str__(self):
2671 return 'contains uncommitted changes' 2706 return 'contains uncommitted changes'
2672 2707
2708
2673class _InfoMessage(object): 2709class _InfoMessage(object):
2710
2674 def __init__(self, project, text): 2711 def __init__(self, project, text):
2675 self.project = project 2712 self.project = project
2676 self.text = text 2713 self.text = text
@@ -2679,7 +2716,9 @@ class _InfoMessage(object):
2679 syncbuf.out.info('%s/: %s', self.project.relpath, self.text) 2716 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2680 syncbuf.out.nl() 2717 syncbuf.out.nl()
2681 2718
2719
2682class _Failure(object): 2720class _Failure(object):
2721
2683 def __init__(self, project, why): 2722 def __init__(self, project, why):
2684 self.project = project 2723 self.project = project
2685 self.why = why 2724 self.why = why
@@ -2690,7 +2729,9 @@ class _Failure(object):
2690 str(self.why)) 2729 str(self.why))
2691 syncbuf.out.nl() 2730 syncbuf.out.nl()
2692 2731
2732
2693class _Later(object): 2733class _Later(object):
2734
2694 def __init__(self, project, action): 2735 def __init__(self, project, action):
2695 self.project = project 2736 self.project = project
2696 self.action = action 2737 self.action = action
@@ -2707,14 +2748,18 @@ class _Later(object):
2707 out.nl() 2748 out.nl()
2708 return False 2749 return False
2709 2750
2751
2710class _SyncColoring(Coloring): 2752class _SyncColoring(Coloring):
2753
2711 def __init__(self, config): 2754 def __init__(self, config):
2712 Coloring.__init__(self, config, 'reposync') 2755 Coloring.__init__(self, config, 'reposync')
2713 self.project = self.printer('header', attr='bold') 2756 self.project = self.printer('header', attr='bold')
2714 self.info = self.printer('info') 2757 self.info = self.printer('info')
2715 self.fail = self.printer('fail', fg='red') 2758 self.fail = self.printer('fail', fg='red')
2716 2759
2760
2717class SyncBuffer(object): 2761class SyncBuffer(object):
2762
2718 def __init__(self, config, detach_head=False): 2763 def __init__(self, config, detach_head=False):
2719 self._messages = [] 2764 self._messages = []
2720 self._failures = [] 2765 self._failures = []
@@ -2770,8 +2815,10 @@ class SyncBuffer(object):
2770 2815
2771 2816
2772class MetaProject(Project): 2817class MetaProject(Project):
2818
2773 """A special project housed under .repo. 2819 """A special project housed under .repo.
2774 """ 2820 """
2821
2775 def __init__(self, manifest, name, gitdir, worktree): 2822 def __init__(self, manifest, name, gitdir, worktree):
2776 Project.__init__(self, 2823 Project.__init__(self,
2777 manifest=manifest, 2824 manifest=manifest,
@@ -2805,10 +2852,9 @@ class MetaProject(Project):
2805 syncbuf.Finish() 2852 syncbuf.Finish()
2806 2853
2807 return GitCommand(self, 2854 return GitCommand(self,
2808 ['update-ref', '-d', 'refs/heads/default'], 2855 ['update-ref', '-d', 'refs/heads/default'],
2809 capture_stdout=True, 2856 capture_stdout=True,
2810 capture_stderr=True).Wait() == 0 2857 capture_stderr=True).Wait() == 0
2811
2812 2858
2813 @property 2859 @property
2814 def LastFetch(self): 2860 def LastFetch(self):
diff --git a/repo b/repo
index 47211742..e5cb8904 100755
--- a/repo
+++ b/repo
@@ -543,7 +543,7 @@ def _DownloadBundle(url, local, quiet):
543 try: 543 try:
544 r = urllib.request.urlopen(url) 544 r = urllib.request.urlopen(url)
545 except urllib.error.HTTPError as e: 545 except urllib.error.HTTPError as e:
546 if e.code in [401, 403, 404]: 546 if e.code in [401, 403, 404, 501]:
547 return False 547 return False
548 _print('fatal: Cannot get %s' % url, file=sys.stderr) 548 _print('fatal: Cannot get %s' % url, file=sys.stderr)
549 _print('fatal: HTTP error %s' % e.code, file=sys.stderr) 549 _print('fatal: HTTP error %s' % e.code, file=sys.stderr)
diff --git a/subcmds/diffmanifests.py b/subcmds/diffmanifests.py
index 05998681..751a2026 100644
--- a/subcmds/diffmanifests.py
+++ b/subcmds/diffmanifests.py
@@ -71,6 +71,10 @@ synced and their revisions won't be found.
71 p.add_option('--no-color', 71 p.add_option('--no-color',
72 dest='color', action='store_false', default=True, 72 dest='color', action='store_false', default=True,
73 help='does not display the diff in color.') 73 help='does not display the diff in color.')
74 p.add_option('--pretty-format',
75 dest='pretty_format', action='store',
76 metavar='<FORMAT>',
77 help='print the log using a custom git pretty format string')
74 78
75 def _printRawDiff(self, diff): 79 def _printRawDiff(self, diff):
76 for project in diff['added']: 80 for project in diff['added']:
@@ -92,7 +96,7 @@ synced and their revisions won't be found.
92 otherProject.revisionExpr)) 96 otherProject.revisionExpr))
93 self.out.nl() 97 self.out.nl()
94 98
95 def _printDiff(self, diff, color=True): 99 def _printDiff(self, diff, color=True, pretty_format=None):
96 if diff['added']: 100 if diff['added']:
97 self.out.nl() 101 self.out.nl()
98 self.printText('added projects : \n') 102 self.printText('added projects : \n')
@@ -124,7 +128,8 @@ synced and their revisions won't be found.
124 self.printText(' to ') 128 self.printText(' to ')
125 self.printRevision(otherProject.revisionExpr) 129 self.printRevision(otherProject.revisionExpr)
126 self.out.nl() 130 self.out.nl()
127 self._printLogs(project, otherProject, raw=False, color=color) 131 self._printLogs(project, otherProject, raw=False, color=color,
132 pretty_format=pretty_format)
128 self.out.nl() 133 self.out.nl()
129 134
130 if diff['unreachable']: 135 if diff['unreachable']:
@@ -139,9 +144,13 @@ synced and their revisions won't be found.
139 self.printText(' not found') 144 self.printText(' not found')
140 self.out.nl() 145 self.out.nl()
141 146
142 def _printLogs(self, project, otherProject, raw=False, color=True): 147 def _printLogs(self, project, otherProject, raw=False, color=True,
143 logs = project.getAddedAndRemovedLogs(otherProject, oneline=True, 148 pretty_format=None):
144 color=color) 149
150 logs = project.getAddedAndRemovedLogs(otherProject,
151 oneline=(pretty_format is None),
152 color=color,
153 pretty_format=pretty_format)
145 if logs['removed']: 154 if logs['removed']:
146 removedLogs = logs['removed'].split('\n') 155 removedLogs = logs['removed'].split('\n')
147 for log in removedLogs: 156 for log in removedLogs:
@@ -192,4 +201,4 @@ synced and their revisions won't be found.
192 if opt.raw: 201 if opt.raw:
193 self._printRawDiff(diff) 202 self._printRawDiff(diff)
194 else: 203 else:
195 self._printDiff(diff, color=opt.color) 204 self._printDiff(diff, color=opt.color, pretty_format=opt.pretty_format)
diff --git a/subcmds/forall.py b/subcmds/forall.py
index b10f34b3..07ee8d58 100644
--- a/subcmds/forall.py
+++ b/subcmds/forall.py
@@ -120,6 +120,9 @@ without iterating through the remaining projects.
120 p.add_option('-r', '--regex', 120 p.add_option('-r', '--regex',
121 dest='regex', action='store_true', 121 dest='regex', action='store_true',
122 help="Execute the command only on projects matching regex or wildcard expression") 122 help="Execute the command only on projects matching regex or wildcard expression")
123 p.add_option('-i', '--inverse-regex',
124 dest='inverse_regex', action='store_true',
125 help="Execute the command only on projects not matching regex or wildcard expression")
123 p.add_option('-g', '--groups', 126 p.add_option('-g', '--groups',
124 dest='groups', 127 dest='groups',
125 help="Execute the command only on projects matching the specified groups") 128 help="Execute the command only on projects matching the specified groups")
@@ -215,10 +218,12 @@ without iterating through the remaining projects.
215 if os.path.isfile(smart_sync_manifest_path): 218 if os.path.isfile(smart_sync_manifest_path):
216 self.manifest.Override(smart_sync_manifest_path) 219 self.manifest.Override(smart_sync_manifest_path)
217 220
218 if not opt.regex: 221 if opt.regex:
219 projects = self.GetProjects(args, groups=opt.groups)
220 else:
221 projects = self.FindProjects(args) 222 projects = self.FindProjects(args)
223 elif opt.inverse_regex:
224 projects = self.FindProjects(args, inverse=True)
225 else:
226 projects = self.GetProjects(args, groups=opt.groups)
222 227
223 os.environ['REPO_COUNT'] = str(len(projects)) 228 os.environ['REPO_COUNT'] = str(len(projects))
224 229
diff --git a/subcmds/sync.py b/subcmds/sync.py
index 4af411c9..9124a653 100644
--- a/subcmds/sync.py
+++ b/subcmds/sync.py
@@ -242,7 +242,7 @@ later is required to fix a server side protocol bug.
242 if show_smart: 242 if show_smart:
243 p.add_option('-s', '--smart-sync', 243 p.add_option('-s', '--smart-sync',
244 dest='smart_sync', action='store_true', 244 dest='smart_sync', action='store_true',
245 help='smart sync using manifest from a known good build') 245 help='smart sync using manifest from the latest known good build')
246 p.add_option('-t', '--smart-tag', 246 p.add_option('-t', '--smart-tag',
247 dest='smart_tag', action='store', 247 dest='smart_tag', action='store',
248 help='smart sync using manifest from a known tag') 248 help='smart sync using manifest from a known tag')