OLD | NEW |
(Empty) | |
| 1 """ |
| 2 Module with abstraction layers to revision control systems. |
| 3 |
| 4 With this library, autotest developers can handle source code checkouts and |
| 5 updates on both client as well as server code. |
| 6 """ |
| 7 |
| 8 import os, warnings, logging |
| 9 import error, utils |
| 10 from autotest_lib.client.bin import os_dep |
| 11 |
| 12 |
| 13 class GitRepo(object): |
| 14 """ |
| 15 This class represents a git repo. |
| 16 |
| 17 It is used to pull down a local copy of a git repo, check if the local |
| 18 repo is up-to-date, if not update. It delegates the install to |
| 19 implementation classes. |
| 20 """ |
| 21 |
| 22 def __init__(self, repodir, giturl, weburl=None): |
| 23 if repodir is None: |
| 24 raise ValueError('You must provide a path that will hold the' |
| 25 'git repository') |
| 26 self.repodir = utils.sh_escape(repodir) |
| 27 if giturl is None: |
| 28 raise ValueError('You must provide a git URL repository') |
| 29 self.giturl = giturl |
| 30 if weburl is not None: |
| 31 warnings.warn("Param weburl: You are no longer required to provide " |
| 32 "a web URL for your git repos", DeprecationWarning) |
| 33 |
| 34 # path to .git dir |
| 35 self.gitpath = utils.sh_escape(os.path.join(self.repodir,'.git')) |
| 36 |
| 37 # Find git base command. If not found, this will throw an exception |
| 38 git_base_cmd = os_dep.command('git') |
| 39 |
| 40 # base git command , pointing to gitpath git dir |
| 41 self.gitcmdbase = '%s --git-dir=%s' % (git_base_cmd, self.gitpath) |
| 42 |
| 43 # default to same remote path as local |
| 44 self._build = os.path.dirname(self.repodir) |
| 45 |
| 46 |
| 47 def _run(self, command, timeout=None, ignore_status=False): |
| 48 """ |
| 49 Auxiliary function to run a command, with proper shell escaping. |
| 50 |
| 51 @param timeout: Timeout to run the command. |
| 52 @param ignore_status: Whether we should supress error.CmdError |
| 53 exceptions if the command did return exit code !=0 (True), or |
| 54 not supress them (False). |
| 55 """ |
| 56 return utils.run(r'%s' % (utils.sh_escape(command)), |
| 57 timeout, ignore_status) |
| 58 |
| 59 |
| 60 def gitcmd(self, cmd, ignore_status=False): |
| 61 """ |
| 62 Wrapper for a git command. |
| 63 |
| 64 @param cmd: Git subcommand (ex 'clone'). |
| 65 @param ignore_status: Whether we should supress error.CmdError |
| 66 exceptions if the command did return exit code !=0 (True), or |
| 67 not supress them (False). |
| 68 """ |
| 69 cmd = '%s %s' % (self.gitcmdbase, cmd) |
| 70 return self._run(cmd, ignore_status=ignore_status) |
| 71 |
| 72 |
| 73 def get(self, **kwargs): |
| 74 """ |
| 75 This method overrides baseclass get so we can do proper git |
| 76 clone/pulls, and check for updated versions. The result of |
| 77 this method will leave an up-to-date version of git repo at |
| 78 'giturl' in 'repodir' directory to be used by build/install |
| 79 methods. |
| 80 |
| 81 @param **kwargs: Dictionary of parameters to the method get. |
| 82 """ |
| 83 if not self.is_repo_initialized(): |
| 84 # this is your first time ... |
| 85 logging.info('Cloning git repo %s', self.giturl) |
| 86 cmd = 'clone %s %s ' % (self.giturl, self.repodir) |
| 87 rv = self.gitcmd(cmd, True) |
| 88 if rv.exit_status != 0: |
| 89 logging.error(rv.stderr) |
| 90 raise error.CmdError('Failed to clone git url', rv) |
| 91 else: |
| 92 logging.info(rv.stdout) |
| 93 |
| 94 else: |
| 95 # exiting repo, check if we're up-to-date |
| 96 if self.is_out_of_date(): |
| 97 logging.info('Updating git repo %s', self.giturl) |
| 98 rv = self.gitcmd('pull', True) |
| 99 if rv.exit_status != 0: |
| 100 logging.error(rv.stderr) |
| 101 e_msg = 'Failed to pull git repo data' |
| 102 raise error.CmdError(e_msg, rv) |
| 103 else: |
| 104 logging.info('repo up-to-date') |
| 105 |
| 106 # remember where the source is |
| 107 self.source_material = self.repodir |
| 108 |
| 109 |
| 110 def get_local_head(self): |
| 111 """ |
| 112 Get the top commit hash of the current local git branch. |
| 113 |
| 114 @return: Top commit hash of local git branch |
| 115 """ |
| 116 cmd = 'log --pretty=format:"%H" -1' |
| 117 l_head_cmd = self.gitcmd(cmd) |
| 118 return l_head_cmd.stdout.strip() |
| 119 |
| 120 |
| 121 def get_remote_head(self): |
| 122 """ |
| 123 Get the top commit hash of the current remote git branch. |
| 124 |
| 125 @return: Top commit hash of remote git branch |
| 126 """ |
| 127 cmd1 = 'remote show' |
| 128 origin_name_cmd = self.gitcmd(cmd1) |
| 129 cmd2 = 'log --pretty=format:"%H" -1 ' + origin_name_cmd.stdout.strip() |
| 130 r_head_cmd = self.gitcmd(cmd2) |
| 131 return r_head_cmd.stdout.strip() |
| 132 |
| 133 |
| 134 def is_out_of_date(self): |
| 135 """ |
| 136 Return whether this branch is out of date with regards to remote branch. |
| 137 |
| 138 @return: False, if the branch is outdated, True if it is current. |
| 139 """ |
| 140 local_head = self.get_local_head() |
| 141 remote_head = self.get_remote_head() |
| 142 |
| 143 # local is out-of-date, pull |
| 144 if local_head != remote_head: |
| 145 return True |
| 146 |
| 147 return False |
| 148 |
| 149 |
| 150 def is_repo_initialized(self): |
| 151 """ |
| 152 Return whether the git repo was already initialized (has a top commit). |
| 153 |
| 154 @return: False, if the repo was initialized, True if it was not. |
| 155 """ |
| 156 cmd = 'log --max-count=1' |
| 157 rv = self.gitcmd(cmd, True) |
| 158 if rv.exit_status == 0: |
| 159 return True |
| 160 |
| 161 return False |
| 162 |
| 163 |
| 164 def get_revision(self): |
| 165 """ |
| 166 Return current HEAD commit id |
| 167 """ |
| 168 if not self.is_repo_initialized(): |
| 169 self.get() |
| 170 |
| 171 cmd = 'rev-parse --verify HEAD' |
| 172 gitlog = self.gitcmd(cmd, True) |
| 173 if gitlog.exit_status != 0: |
| 174 logging.error(gitlog.stderr) |
| 175 raise error.CmdError('Failed to find git sha1 revision', gitlog) |
| 176 else: |
| 177 return gitlog.stdout.strip('\n') |
| 178 |
| 179 |
| 180 def checkout(self, remote, local=None): |
| 181 """ |
| 182 Check out the git commit id, branch, or tag given by remote. |
| 183 |
| 184 Optional give the local branch name as local. |
| 185 |
| 186 @param remote: Remote commit hash |
| 187 @param local: Local commit hash |
| 188 @note: For git checkout tag git version >= 1.5.0 is required |
| 189 """ |
| 190 if not self.is_repo_initialized(): |
| 191 self.get() |
| 192 |
| 193 assert(isinstance(remote, basestring)) |
| 194 if local: |
| 195 cmd = 'checkout -b %s %s' % (local, remote) |
| 196 else: |
| 197 cmd = 'checkout %s' % (remote) |
| 198 gitlog = self.gitcmd(cmd, True) |
| 199 if gitlog.exit_status != 0: |
| 200 logging.error(gitlog.stderr) |
| 201 raise error.CmdError('Failed to checkout git branch', gitlog) |
| 202 else: |
| 203 logging.info(gitlog.stdout) |
| 204 |
| 205 |
| 206 def get_branch(self, all=False, remote_tracking=False): |
| 207 """ |
| 208 Show the branches. |
| 209 |
| 210 @param all: List both remote-tracking branches and local branches (True) |
| 211 or only the local ones (False). |
| 212 @param remote_tracking: Lists the remote-tracking branches. |
| 213 """ |
| 214 if not self.is_repo_initialized(): |
| 215 self.get() |
| 216 |
| 217 cmd = 'branch --no-color' |
| 218 if all: |
| 219 cmd = " ".join([cmd, "-a"]) |
| 220 if remote_tracking: |
| 221 cmd = " ".join([cmd, "-r"]) |
| 222 |
| 223 gitlog = self.gitcmd(cmd, True) |
| 224 if gitlog.exit_status != 0: |
| 225 logging.error(gitlog.stderr) |
| 226 raise error.CmdError('Failed to get git branch', gitlog) |
| 227 elif all or remote_tracking: |
| 228 return gitlog.stdout.strip('\n') |
| 229 else: |
| 230 branch = [b[2:] for b in gitlog.stdout.split('\n') |
| 231 if b.startswith('*')][0] |
| 232 return branch |
OLD | NEW |