virt.virt_test_utils: run_autotest - 'tar' needs relative paths to strip the leading '/'
[autotest-zwu.git] / client / common_lib / revision_control.py
blob931e5a5ca54095d7aa2280d9107b0a912454e343
1 """
2 Module with abstraction layers to revision control systems.
4 With this library, autotest developers can handle source code checkouts and
5 updates on both client as well as server code.
6 """
8 import os, warnings, logging
9 import error, utils
10 from autotest_lib.client.bin import os_dep
13 class GitRepo(object):
14 """
15 This class represents a git repo.
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 """
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)
34 # path to .git dir
35 self.gitpath = utils.sh_escape(os.path.join(self.repodir,'.git'))
37 # Find git base command. If not found, this will throw an exception
38 git_base_cmd = os_dep.command('git')
40 # base git command , pointing to gitpath git dir
41 self.gitcmdbase = '%s --git-dir=%s' % (git_base_cmd, self.gitpath)
43 # default to same remote path as local
44 self._build = os.path.dirname(self.repodir)
47 def _run(self, command, timeout=None, ignore_status=False):
48 """
49 Auxiliary function to run a command, with proper shell escaping.
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)
60 def gitcmd(self, cmd, ignore_status=False):
61 """
62 Wrapper for a git command.
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)
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.
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)
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')
106 # remember where the source is
107 self.source_material = self.repodir
110 def get_local_head(self):
112 Get the top commit hash of the current local git branch.
114 @return: Top commit hash of local git branch
116 cmd = 'log --pretty=format:"%H" -1'
117 l_head_cmd = self.gitcmd(cmd)
118 return l_head_cmd.stdout.strip()
121 def get_remote_head(self):
123 Get the top commit hash of the current remote git branch.
125 @return: Top commit hash of remote git branch
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()
134 def is_out_of_date(self):
136 Return whether this branch is out of date with regards to remote branch.
138 @return: False, if the branch is outdated, True if it is current.
140 local_head = self.get_local_head()
141 remote_head = self.get_remote_head()
143 # local is out-of-date, pull
144 if local_head != remote_head:
145 return True
147 return False
150 def is_repo_initialized(self):
152 Return whether the git repo was already initialized (has a top commit).
154 @return: False, if the repo was initialized, True if it was not.
156 cmd = 'log --max-count=1'
157 rv = self.gitcmd(cmd, True)
158 if rv.exit_status == 0:
159 return True
161 return False
164 def get_revision(self):
166 Return current HEAD commit id
168 if not self.is_repo_initialized():
169 self.get()
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')
180 def checkout(self, remote, local=None):
182 Check out the git commit id, branch, or tag given by remote.
184 Optional give the local branch name as local.
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
190 if not self.is_repo_initialized():
191 self.get()
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)
206 def get_branch(self, all=False, remote_tracking=False):
208 Show the branches.
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.
214 if not self.is_repo_initialized():
215 self.get()
217 cmd = 'branch --no-color'
218 if all:
219 cmd = " ".join([cmd, "-a"])
220 if remote_tracking:
221 cmd = " ".join([cmd, "-r"])
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