| OLD | NEW |
| (Empty) | |
| 1 # Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. |
| 4 |
| 5 import logging |
| 6 import os |
| 7 from urlparse import urlparse |
| 8 import subprocess |
| 9 import threading |
| 10 |
| 11 from git_checkout import local_git_parsers |
| 12 from lib.gitiles.git_repository import GitRepository |
| 13 import script_util |
| 14 |
| 15 _CHANGELOG_FORMAT_STRING = ('commit %H%n' |
| 16 'author %an%n' |
| 17 'author-mail %ae%n' |
| 18 'author-time %ad%n%n' |
| 19 'committer %cn%n' |
| 20 'committer-mail %ce%n' |
| 21 'committer-time %cd%n%n' |
| 22 '--Message start--%n%B%n--Message end--%n') |
| 23 _CHANGELOGS_FORMAT_STRING = ('**Changelog start**%%n%s' % |
| 24 _CHANGELOG_FORMAT_STRING) |
| 25 CHECKOUT_ROOT_DIR = os.path.join(os.path.expanduser('~'), '.local_checkouts') |
| 26 |
| 27 |
| 28 def ConvertRemoteCommitToLocal(revision): |
| 29 """Converts remote commit from gitile to local git checkout revision.""" |
| 30 return 'HEAD' if revision == 'master' else revision |
| 31 |
| 32 |
| 33 class LocalGitRepository(GitRepository): |
| 34 """Represents local checkout of git repository on chromium host. |
| 35 |
| 36 Note, to automatically check out internal repos which you have access to, |
| 37 follow the instructions in 'go/internal-repo-checkout-setup'. |
| 38 """ |
| 39 lock = threading.Lock() |
| 40 # Keep track all the updated repos, so every repo only get updated once. |
| 41 _updated_repos = set() |
| 42 |
| 43 def __init__(self, repo_url=None): |
| 44 self._host = None |
| 45 self._repo_path = None |
| 46 self._repo_url = None |
| 47 self.repo_url = repo_url |
| 48 self.changelog_parser = local_git_parsers.GitChangeLogParser() |
| 49 self.changelogs_parser = local_git_parsers.GitChangeLogsParser() |
| 50 self.blame_parser = local_git_parsers.GitBlameParser() |
| 51 self.diff_parser = local_git_parsers.GitDiffParser() |
| 52 |
| 53 @property |
| 54 def repo_path(self): |
| 55 return self._repo_path |
| 56 |
| 57 @property |
| 58 def real_repo_path(self): |
| 59 """Absolute path of the local repository.""" |
| 60 return os.path.join(CHECKOUT_ROOT_DIR, self._host, self.repo_path) |
| 61 |
| 62 @property |
| 63 def repo_url(self): |
| 64 """Url of remote repository which the local repo checks out from.""" |
| 65 return self._repo_url |
| 66 |
| 67 @repo_url.setter |
| 68 def repo_url(self, repo_url): |
| 69 if self._repo_url == repo_url: |
| 70 return |
| 71 |
| 72 self._repo_url = repo_url |
| 73 if not self._repo_url: |
| 74 return |
| 75 |
| 76 parsed_url = urlparse(repo_url) |
| 77 self._host = parsed_url.netloc |
| 78 # Remove the / in the front of path. |
| 79 self._repo_path = parsed_url.path[1:] |
| 80 |
| 81 self._CloneOrUpdateRepoIfNeeded() |
| 82 |
| 83 def _CloneOrUpdateRepoIfNeeded(self): |
| 84 """Clones repo, or update it if it didn't got updated before.""" |
| 85 if self.repo_url in LocalGitRepository._updated_repos: |
| 86 return |
| 87 |
| 88 with LocalGitRepository.lock: |
| 89 # Clone the repo if needed. |
| 90 if not os.path.exists(self.real_repo_path): |
| 91 try: |
| 92 subprocess.check_call(['git', 'clone', |
| 93 self.repo_url, self.real_repo_path]) |
| 94 except subprocess.CalledProcessError as e: # pragma: no cover. |
| 95 logging.error('Exception while cloning %s: %s', self.repo_url, e) |
| 96 return |
| 97 # Update repo if it's already cloned. |
| 98 else: |
| 99 try: |
| 100 # Disable verbose of cd and git pull. |
| 101 with open(os.devnull, 'w') as null_handle: |
| 102 subprocess.check_call( |
| 103 'cd %s && git pull' % self.real_repo_path, |
| 104 stdout=null_handle, stderr=null_handle, shell=True) |
| 105 except subprocess.CalledProcessError as e: # pragma: no cover. |
| 106 logging.error('Exception while updating %s: %s', self.repo_path, e) |
| 107 return |
| 108 |
| 109 LocalGitRepository._updated_repos.add(self.repo_url) |
| 110 |
| 111 def _GetFinalCommand(self, command, utc=False): |
| 112 # Change local time to utc time. |
| 113 if utc: |
| 114 command = 'TZ=UTC %s --date=format-local:"%s"' % ( |
| 115 command, local_git_parsers.DATETIME_FORMAT) |
| 116 return 'cd %s && %s' % (self.real_repo_path, command) |
| 117 |
| 118 def GetChangeLog(self, revision): |
| 119 """Returns the change log of the given revision.""" |
| 120 command = ('git log --pretty=format:"%s" --max-count=1 --raw ' |
| 121 '--no-abbrev %s' % (_CHANGELOG_FORMAT_STRING, |
| 122 ConvertRemoteCommitToLocal(revision))) |
| 123 output = script_util.GetCommandOutput(self._GetFinalCommand(command, True)) |
| 124 return self.changelog_parser(output, self.repo_url) |
| 125 |
| 126 def GetChangeLogs(self, start_revision, end_revision): # pylint: disable=W |
| 127 """Returns change log list in (start_revision, end_revision].""" |
| 128 command = ('git log --pretty=format:"%s" --raw --no-abbrev %s' % ( |
| 129 _CHANGELOGS_FORMAT_STRING, |
| 130 '%s..%s' % (ConvertRemoteCommitToLocal(start_revision), |
| 131 ConvertRemoteCommitToLocal(end_revision)))) |
| 132 output = script_util.GetCommandOutput(self._GetFinalCommand(command, True)) |
| 133 return self.changelogs_parser(output, self.repo_url) |
| 134 |
| 135 def GetChangeDiff(self, revision, path=None): # pylint: disable=W |
| 136 """Returns the diff of the given revision.""" |
| 137 command = ('git log --format="" --max-count=1 %s' % |
| 138 ConvertRemoteCommitToLocal(revision)) |
| 139 if path: |
| 140 command += ' -p %s' % path |
| 141 output = script_util.GetCommandOutput(self._GetFinalCommand(command)) |
| 142 return self.diff_parser(output) |
| 143 |
| 144 def GetBlame(self, path, revision): |
| 145 """Returns blame of the file at ``path`` of the given revision.""" |
| 146 command = 'git blame --incremental %s %s' % ( |
| 147 path, ConvertRemoteCommitToLocal(revision)) |
| 148 output = script_util.GetCommandOutput(self._GetFinalCommand(command)) |
| 149 return self.blame_parser(output, path, revision) |
| 150 |
| 151 def GetSource(self, path, revision): |
| 152 """Returns source code of the file at ``path`` of the given revision.""" |
| 153 # Check whether the requested file exist or not. |
| 154 command = 'git show %s:%s' % (ConvertRemoteCommitToLocal(revision), path) |
| 155 output = script_util.GetCommandOutput(self._GetFinalCommand(command)) |
| 156 return output |
| OLD | NEW |