OLD | NEW |
1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 """Gclient-specific SCM-specific operations.""" | 5 """Gclient-specific SCM-specific operations.""" |
6 | 6 |
7 import collections | 7 import collections |
8 import logging | 8 import logging |
9 import os | 9 import os |
10 import posixpath | 10 import posixpath |
(...skipping 204 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
215 result, version = scm.GIT.AssertVersion('1.7') | 215 result, version = scm.GIT.AssertVersion('1.7') |
216 if not result: | 216 if not result: |
217 raise gclient_utils.Error('Git version is older than 1.7: %s' % version) | 217 raise gclient_utils.Error('Git version is older than 1.7: %s' % version) |
218 return result | 218 return result |
219 except OSError: | 219 except OSError: |
220 return False | 220 return False |
221 | 221 |
222 def GetCheckoutRoot(self): | 222 def GetCheckoutRoot(self): |
223 return scm.GIT.GetCheckoutRoot(self.checkout_path) | 223 return scm.GIT.GetCheckoutRoot(self.checkout_path) |
224 | 224 |
225 def GetRemoteURL(self, options, cwd=None): | |
226 try: | |
227 return self._Capture(['config', 'remote.%s.url' % self.remote], | |
228 cwd=cwd or self.checkout_path).rstrip() | |
229 except (OSError, subprocess2.CalledProcessError): | |
230 return None | |
231 | |
232 def GetRevisionDate(self, _revision): | 225 def GetRevisionDate(self, _revision): |
233 """Returns the given revision's date in ISO-8601 format (which contains the | 226 """Returns the given revision's date in ISO-8601 format (which contains the |
234 time zone).""" | 227 time zone).""" |
235 # TODO(floitsch): get the time-stamp of the given revision and not just the | 228 # TODO(floitsch): get the time-stamp of the given revision and not just the |
236 # time-stamp of the currently checked out revision. | 229 # time-stamp of the currently checked out revision. |
237 return self._Capture(['log', '-n', '1', '--format=%ai']) | 230 return self._Capture(['log', '-n', '1', '--format=%ai']) |
238 | 231 |
239 @staticmethod | 232 @staticmethod |
240 def cleanup(options, args, file_list): | 233 def cleanup(options, args, file_list): |
241 """'Cleanup' the repo. | 234 """'Cleanup' the repo. |
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
279 # Not a fatal error, or even very interesting in a non-git-submodule | 272 # Not a fatal error, or even very interesting in a non-git-submodule |
280 # world. So just keep it quiet. | 273 # world. So just keep it quiet. |
281 pass | 274 pass |
282 try: | 275 try: |
283 gclient_utils.CheckCallAndFilter(cmd3, **kwargs) | 276 gclient_utils.CheckCallAndFilter(cmd3, **kwargs) |
284 except subprocess2.CalledProcessError: | 277 except subprocess2.CalledProcessError: |
285 gclient_utils.CheckCallAndFilter(cmd3 + ['always'], **kwargs) | 278 gclient_utils.CheckCallAndFilter(cmd3 + ['always'], **kwargs) |
286 | 279 |
287 gclient_utils.CheckCallAndFilter(cmd4, **kwargs) | 280 gclient_utils.CheckCallAndFilter(cmd4, **kwargs) |
288 | 281 |
| 282 def _FetchAndReset(self, revision, file_list, options): |
| 283 """Equivalent to git fetch; git reset.""" |
| 284 quiet = [] |
| 285 if not options.verbose: |
| 286 quiet = ['--quiet'] |
| 287 self._UpdateBranchHeads(options, fetch=False) |
| 288 |
| 289 fetch_cmd = [ |
| 290 '-c', 'core.deltaBaseCacheLimit=2g', 'fetch', self.remote, '--prune'] |
| 291 self._Run(fetch_cmd + quiet, options, retry=True) |
| 292 self._Run(['reset', '--hard', revision] + quiet, options) |
| 293 self.UpdateSubmoduleConfig() |
| 294 if file_list is not None: |
| 295 files = self._Capture(['ls-files']).splitlines() |
| 296 file_list.extend([os.path.join(self.checkout_path, f) for f in files]) |
| 297 |
289 def update(self, options, args, file_list): | 298 def update(self, options, args, file_list): |
290 """Runs git to update or transparently checkout the working copy. | 299 """Runs git to update or transparently checkout the working copy. |
291 | 300 |
292 All updated files will be appended to file_list. | 301 All updated files will be appended to file_list. |
293 | 302 |
294 Raises: | 303 Raises: |
295 Error: if can't get URL for relative path. | 304 Error: if can't get URL for relative path. |
296 """ | 305 """ |
297 if args: | 306 if args: |
298 raise gclient_utils.Error("Unsupported argument(s): %s" % ",".join(args)) | 307 raise gclient_utils.Error("Unsupported argument(s): %s" % ",".join(args)) |
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
365 return self._Capture(['rev-parse', '--verify', 'HEAD']) | 374 return self._Capture(['rev-parse', '--verify', 'HEAD']) |
366 | 375 |
367 if not os.path.exists(os.path.join(self.checkout_path, '.git')): | 376 if not os.path.exists(os.path.join(self.checkout_path, '.git')): |
368 raise gclient_utils.Error('\n____ %s%s\n' | 377 raise gclient_utils.Error('\n____ %s%s\n' |
369 '\tPath is not a git repo. No .git dir.\n' | 378 '\tPath is not a git repo. No .git dir.\n' |
370 '\tTo resolve:\n' | 379 '\tTo resolve:\n' |
371 '\t\trm -rf %s\n' | 380 '\t\trm -rf %s\n' |
372 '\tAnd run gclient sync again\n' | 381 '\tAnd run gclient sync again\n' |
373 % (self.relpath, rev_str, self.relpath)) | 382 % (self.relpath, rev_str, self.relpath)) |
374 | 383 |
| 384 # See if the url has changed (the unittests use git://foo for the url, let |
| 385 # that through). |
| 386 current_url = self._Capture(['config', 'remote.%s.url' % self.remote]) |
| 387 return_early = False |
| 388 # TODO(maruel): Delete url != 'git://foo' since it's just to make the |
| 389 # unit test pass. (and update the comment above) |
| 390 # Skip url auto-correction if remote.origin.gclient-auto-fix-url is set. |
| 391 # This allows devs to use experimental repos which have a different url |
| 392 # but whose branch(s) are the same as official repos. |
| 393 if (current_url != url and |
| 394 url != 'git://foo' and |
| 395 subprocess2.capture( |
| 396 ['git', 'config', 'remote.%s.gclient-auto-fix-url' % self.remote], |
| 397 cwd=self.checkout_path).strip() != 'False'): |
| 398 print('_____ switching %s to a new upstream' % self.relpath) |
| 399 # Make sure it's clean |
| 400 self._CheckClean(rev_str) |
| 401 # Switch over to the new upstream |
| 402 self._Run(['remote', 'set-url', self.remote, url], options) |
| 403 self._FetchAndReset(revision, file_list, options) |
| 404 return_early = True |
| 405 |
375 # Need to do this in the normal path as well as in the post-remote-switch | 406 # Need to do this in the normal path as well as in the post-remote-switch |
376 # path. | 407 # path. |
377 self._PossiblySwitchCache(url, options) | 408 self._PossiblySwitchCache(url, options) |
378 | 409 |
| 410 if return_early: |
| 411 return self._Capture(['rev-parse', '--verify', 'HEAD']) |
| 412 |
379 cur_branch = self._GetCurrentBranch() | 413 cur_branch = self._GetCurrentBranch() |
380 | 414 |
381 # Cases: | 415 # Cases: |
382 # 0) HEAD is detached. Probably from our initial clone. | 416 # 0) HEAD is detached. Probably from our initial clone. |
383 # - make sure HEAD is contained by a named ref, then update. | 417 # - make sure HEAD is contained by a named ref, then update. |
384 # Cases 1-4. HEAD is a branch. | 418 # Cases 1-4. HEAD is a branch. |
385 # 1) current branch is not tracking a remote branch (could be git-svn) | 419 # 1) current branch is not tracking a remote branch (could be git-svn) |
386 # - try to rebase onto the new hash or branch | 420 # - try to rebase onto the new hash or branch |
387 # 2) current branch is tracking a remote branch with local committed | 421 # 2) current branch is tracking a remote branch with local committed |
388 # changes, but the DEPS file switched to point to a hash | 422 # changes, but the DEPS file switched to point to a hash |
(...skipping 385 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
774 if use_reference: | 808 if use_reference: |
775 cmd += ['--reference', os.path.abspath(self.checkout_path)] | 809 cmd += ['--reference', os.path.abspath(self.checkout_path)] |
776 | 810 |
777 self._Run(cmd + [url, folder], | 811 self._Run(cmd + [url, folder], |
778 options, filter_fn=filter_fn, cwd=self.cache_dir, retry=True) | 812 options, filter_fn=filter_fn, cwd=self.cache_dir, retry=True) |
779 else: | 813 else: |
780 # For now, assert that host/path/to/repo.git is identical. We may want | 814 # For now, assert that host/path/to/repo.git is identical. We may want |
781 # to relax this restriction in the future to allow for smarter cache | 815 # to relax this restriction in the future to allow for smarter cache |
782 # repo update schemes (such as pulling the same repo, but from a | 816 # repo update schemes (such as pulling the same repo, but from a |
783 # different host). | 817 # different host). |
784 existing_url = self.GetRemoteURL(options, cwd=folder) | 818 existing_url = self._Capture(['config', 'remote.%s.url' % self.remote], |
| 819 cwd=folder) |
785 assert self._NormalizeGitURL(existing_url) == self._NormalizeGitURL(url) | 820 assert self._NormalizeGitURL(existing_url) == self._NormalizeGitURL(url) |
786 | 821 |
787 if use_reference: | 822 if use_reference: |
788 with open(altfile, 'w') as f: | 823 with open(altfile, 'w') as f: |
789 f.write(os.path.abspath(checkout_objects)) | 824 f.write(os.path.abspath(checkout_objects)) |
790 | 825 |
791 # Would normally use `git remote update`, but it doesn't support | 826 # Would normally use `git remote update`, but it doesn't support |
792 # --progress, so use fetch instead. | 827 # --progress, so use fetch instead. |
793 self._Run(['fetch'] + v + ['--multiple', '--progress', '--all'], | 828 self._Run(['fetch'] + v + ['--multiple', '--progress', '--all'], |
794 options, filter_fn=filter_fn, cwd=folder, retry=True) | 829 options, filter_fn=filter_fn, cwd=folder, retry=True) |
(...skipping 248 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1043 result, version = scm.SVN.AssertVersion('1.4') | 1078 result, version = scm.SVN.AssertVersion('1.4') |
1044 if not result: | 1079 if not result: |
1045 raise gclient_utils.Error('SVN version is older than 1.4: %s' % version) | 1080 raise gclient_utils.Error('SVN version is older than 1.4: %s' % version) |
1046 return result | 1081 return result |
1047 except OSError: | 1082 except OSError: |
1048 return False | 1083 return False |
1049 | 1084 |
1050 def GetCheckoutRoot(self): | 1085 def GetCheckoutRoot(self): |
1051 return scm.SVN.GetCheckoutRoot(self.checkout_path) | 1086 return scm.SVN.GetCheckoutRoot(self.checkout_path) |
1052 | 1087 |
1053 def GetRemoteURL(self, options): | |
1054 try: | |
1055 local_info = scm.SVN.CaptureLocalInfo([os.curdir], self.checkout_path) | |
1056 except (OSError, subprocess2.CalledProcessError): | |
1057 return None | |
1058 return local_info.get('URL') | |
1059 | |
1060 def GetRevisionDate(self, revision): | 1088 def GetRevisionDate(self, revision): |
1061 """Returns the given revision's date in ISO-8601 format (which contains the | 1089 """Returns the given revision's date in ISO-8601 format (which contains the |
1062 time zone).""" | 1090 time zone).""" |
1063 date = scm.SVN.Capture( | 1091 date = scm.SVN.Capture( |
1064 ['propget', '--revprop', 'svn:date', '-r', revision], | 1092 ['propget', '--revprop', 'svn:date', '-r', revision], |
1065 os.path.join(self.checkout_path, '.')) | 1093 os.path.join(self.checkout_path, '.')) |
1066 return date.strip() | 1094 return date.strip() |
1067 | 1095 |
1068 def cleanup(self, options, args, _file_list): | 1096 def cleanup(self, options, args, _file_list): |
1069 """Cleanup working copy.""" | 1097 """Cleanup working copy.""" |
(...skipping 19 matching lines...) Expand all Loading... |
1089 filter_fn=SvnDiffFilterer(self.relpath).Filter) | 1117 filter_fn=SvnDiffFilterer(self.relpath).Filter) |
1090 | 1118 |
1091 def update(self, options, args, file_list): | 1119 def update(self, options, args, file_list): |
1092 """Runs svn to update or transparently checkout the working copy. | 1120 """Runs svn to update or transparently checkout the working copy. |
1093 | 1121 |
1094 All updated files will be appended to file_list. | 1122 All updated files will be appended to file_list. |
1095 | 1123 |
1096 Raises: | 1124 Raises: |
1097 Error: if can't get URL for relative path. | 1125 Error: if can't get URL for relative path. |
1098 """ | 1126 """ |
| 1127 # Only update if git or hg is not controlling the directory. |
| 1128 git_path = os.path.join(self.checkout_path, '.git') |
| 1129 if os.path.exists(git_path): |
| 1130 print('________ found .git directory; skipping %s' % self.relpath) |
| 1131 return |
| 1132 |
| 1133 hg_path = os.path.join(self.checkout_path, '.hg') |
| 1134 if os.path.exists(hg_path): |
| 1135 print('________ found .hg directory; skipping %s' % self.relpath) |
| 1136 return |
| 1137 |
1099 if args: | 1138 if args: |
1100 raise gclient_utils.Error("Unsupported argument(s): %s" % ",".join(args)) | 1139 raise gclient_utils.Error("Unsupported argument(s): %s" % ",".join(args)) |
1101 | 1140 |
1102 # revision is the revision to match. It is None if no revision is specified, | 1141 # revision is the revision to match. It is None if no revision is specified, |
1103 # i.e. the 'deps ain't pinned'. | 1142 # i.e. the 'deps ain't pinned'. |
1104 url, revision = gclient_utils.SplitUrlRevision(self.url) | 1143 url, revision = gclient_utils.SplitUrlRevision(self.url) |
| 1144 # Keep the original unpinned url for reference in case the repo is switched. |
| 1145 base_url = url |
1105 managed = True | 1146 managed = True |
1106 if options.revision: | 1147 if options.revision: |
1107 # Override the revision number. | 1148 # Override the revision number. |
1108 revision = str(options.revision) | 1149 revision = str(options.revision) |
1109 if revision: | 1150 if revision: |
1110 if revision != 'unmanaged': | 1151 if revision != 'unmanaged': |
1111 forced_revision = True | 1152 forced_revision = True |
1112 # Reconstruct the url. | 1153 # Reconstruct the url. |
1113 url = '%s@%s' % (url, revision) | 1154 url = '%s@%s' % (url, revision) |
1114 rev_str = ' at %s' % revision | 1155 rev_str = ' at %s' % revision |
(...skipping 126 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1241 if d[0][0] == '!': | 1282 if d[0][0] == '!': |
1242 print 'You can pass --force to enable automatic removal.' | 1283 print 'You can pass --force to enable automatic removal.' |
1243 raise e | 1284 raise e |
1244 | 1285 |
1245 # Retrieve the current HEAD version because svn is slow at null updates. | 1286 # Retrieve the current HEAD version because svn is slow at null updates. |
1246 if options.manually_grab_svn_rev and not revision: | 1287 if options.manually_grab_svn_rev and not revision: |
1247 from_info_live = scm.SVN.CaptureRemoteInfo(from_info['URL']) | 1288 from_info_live = scm.SVN.CaptureRemoteInfo(from_info['URL']) |
1248 revision = str(from_info_live['Revision']) | 1289 revision = str(from_info_live['Revision']) |
1249 rev_str = ' at %s' % revision | 1290 rev_str = ' at %s' % revision |
1250 | 1291 |
| 1292 if from_info['URL'] != base_url: |
| 1293 # The repository url changed, need to switch. |
| 1294 try: |
| 1295 to_info = scm.SVN.CaptureRemoteInfo(url) |
| 1296 except (gclient_utils.Error, subprocess2.CalledProcessError): |
| 1297 # The url is invalid or the server is not accessible, it's safer to bail |
| 1298 # out right now. |
| 1299 raise gclient_utils.Error('This url is unreachable: %s' % url) |
| 1300 can_switch = ((from_info['Repository Root'] != to_info['Repository Root']) |
| 1301 and (from_info['UUID'] == to_info['UUID'])) |
| 1302 if can_switch: |
| 1303 print('\n_____ relocating %s to a new checkout' % self.relpath) |
| 1304 # We have different roots, so check if we can switch --relocate. |
| 1305 # Subversion only permits this if the repository UUIDs match. |
| 1306 # Perform the switch --relocate, then rewrite the from_url |
| 1307 # to reflect where we "are now." (This is the same way that |
| 1308 # Subversion itself handles the metadata when switch --relocate |
| 1309 # is used.) This makes the checks below for whether we |
| 1310 # can update to a revision or have to switch to a different |
| 1311 # branch work as expected. |
| 1312 # TODO(maruel): TEST ME ! |
| 1313 command = ['switch', '--relocate', |
| 1314 from_info['Repository Root'], |
| 1315 to_info['Repository Root'], |
| 1316 self.relpath] |
| 1317 self._Run(command, options, cwd=self._root_dir) |
| 1318 from_info['URL'] = from_info['URL'].replace( |
| 1319 from_info['Repository Root'], |
| 1320 to_info['Repository Root']) |
| 1321 else: |
| 1322 if not options.force and not options.reset: |
| 1323 # Look for local modifications but ignore unversioned files. |
| 1324 for status in scm.SVN.CaptureStatus(None, self.checkout_path): |
| 1325 if status[0][0] != '?': |
| 1326 raise gclient_utils.Error( |
| 1327 ('Can\'t switch the checkout to %s; UUID don\'t match and ' |
| 1328 'there is local changes in %s. Delete the directory and ' |
| 1329 'try again.') % (url, self.checkout_path)) |
| 1330 # Ok delete it. |
| 1331 print('\n_____ switching %s to a new checkout' % self.relpath) |
| 1332 gclient_utils.rmtree(self.checkout_path) |
| 1333 # We need to checkout. |
| 1334 command = ['checkout', url, self.checkout_path] |
| 1335 command = self._AddAdditionalUpdateFlags(command, options, revision) |
| 1336 self._RunAndGetFileList(command, options, file_list, self._root_dir) |
| 1337 return self.Svnversion() |
| 1338 |
1251 # If the provided url has a revision number that matches the revision | 1339 # If the provided url has a revision number that matches the revision |
1252 # number of the existing directory, then we don't need to bother updating. | 1340 # number of the existing directory, then we don't need to bother updating. |
1253 if not options.force and str(from_info['Revision']) == revision: | 1341 if not options.force and str(from_info['Revision']) == revision: |
1254 if options.verbose or not forced_revision: | 1342 if options.verbose or not forced_revision: |
1255 print('\n_____ %s%s' % (self.relpath, rev_str)) | 1343 print('\n_____ %s%s' % (self.relpath, rev_str)) |
1256 else: | 1344 else: |
1257 command = ['update', self.checkout_path] | 1345 command = ['update', self.checkout_path] |
1258 command = self._AddAdditionalUpdateFlags(command, options, revision) | 1346 command = self._AddAdditionalUpdateFlags(command, options, revision) |
1259 self._RunAndGetFileList(command, options, file_list, self._root_dir) | 1347 self._RunAndGetFileList(command, options, file_list, self._root_dir) |
1260 | 1348 |
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1307 if not os.path.isdir(self.checkout_path): | 1395 if not os.path.isdir(self.checkout_path): |
1308 if os.path.exists(self.checkout_path): | 1396 if os.path.exists(self.checkout_path): |
1309 gclient_utils.rmtree(self.checkout_path) | 1397 gclient_utils.rmtree(self.checkout_path) |
1310 # svn revert won't work if the directory doesn't exist. It needs to | 1398 # svn revert won't work if the directory doesn't exist. It needs to |
1311 # checkout instead. | 1399 # checkout instead. |
1312 print('\n_____ %s is missing, synching instead' % self.relpath) | 1400 print('\n_____ %s is missing, synching instead' % self.relpath) |
1313 # Don't reuse the args. | 1401 # Don't reuse the args. |
1314 return self.update(options, [], file_list) | 1402 return self.update(options, [], file_list) |
1315 | 1403 |
1316 if not os.path.isdir(os.path.join(self.checkout_path, '.svn')): | 1404 if not os.path.isdir(os.path.join(self.checkout_path, '.svn')): |
| 1405 if os.path.isdir(os.path.join(self.checkout_path, '.git')): |
| 1406 print('________ found .git directory; skipping %s' % self.relpath) |
| 1407 return |
| 1408 if os.path.isdir(os.path.join(self.checkout_path, '.hg')): |
| 1409 print('________ found .hg directory; skipping %s' % self.relpath) |
| 1410 return |
1317 if not options.force: | 1411 if not options.force: |
1318 raise gclient_utils.Error('Invalid checkout path, aborting') | 1412 raise gclient_utils.Error('Invalid checkout path, aborting') |
1319 print( | 1413 print( |
1320 '\n_____ %s is not a valid svn checkout, synching instead' % | 1414 '\n_____ %s is not a valid svn checkout, synching instead' % |
1321 self.relpath) | 1415 self.relpath) |
1322 gclient_utils.rmtree(self.checkout_path) | 1416 gclient_utils.rmtree(self.checkout_path) |
1323 # Don't reuse the args. | 1417 # Don't reuse the args. |
1324 return self.update(options, [], file_list) | 1418 return self.update(options, [], file_list) |
1325 | 1419 |
1326 def printcb(file_status): | 1420 def printcb(file_status): |
(...skipping 97 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1424 new_command.append('--force') | 1518 new_command.append('--force') |
1425 if command[0] != 'checkout' and scm.SVN.AssertVersion('1.6')[0]: | 1519 if command[0] != 'checkout' and scm.SVN.AssertVersion('1.6')[0]: |
1426 new_command.extend(('--accept', 'theirs-conflict')) | 1520 new_command.extend(('--accept', 'theirs-conflict')) |
1427 elif options.manually_grab_svn_rev: | 1521 elif options.manually_grab_svn_rev: |
1428 new_command.append('--force') | 1522 new_command.append('--force') |
1429 if command[0] != 'checkout' and scm.SVN.AssertVersion('1.6')[0]: | 1523 if command[0] != 'checkout' and scm.SVN.AssertVersion('1.6')[0]: |
1430 new_command.extend(('--accept', 'postpone')) | 1524 new_command.extend(('--accept', 'postpone')) |
1431 elif command[0] != 'checkout' and scm.SVN.AssertVersion('1.6')[0]: | 1525 elif command[0] != 'checkout' and scm.SVN.AssertVersion('1.6')[0]: |
1432 new_command.extend(('--accept', 'postpone')) | 1526 new_command.extend(('--accept', 'postpone')) |
1433 return new_command | 1527 return new_command |
OLD | NEW |