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 |