Introduce a library with classes to abstract interaction with version control system repositories. Right now, there is only GitRepo implemented. Made server/git.py to use the common_lib implementation (the server git class represents the install of a git repo in a host).
Now, test writers and can use a shared API to check out and update git code conveniently. Signed-off-by: Lucas Meneghel Rodrigues <[email protected]> --- client/common_lib/revision_control.py | 232 +++++++++++++++++++++++++++++++++ server/git.py | 150 +++------------------- server/git_kernel.py | 5 +- 3 files changed, 252 insertions(+), 135 deletions(-) create mode 100644 client/common_lib/revision_control.py diff --git a/client/common_lib/revision_control.py b/client/common_lib/revision_control.py new file mode 100644 index 0000000..79adfff --- /dev/null +++ b/client/common_lib/revision_control.py @@ -0,0 +1,232 @@ +""" +Module with abstraction layers to revision control systems. + +With this library, autotest developers can handle source code checkouts and +updates on both client as well as server code. +""" + +import os, warnings, logging +import error, utils +from autotest_lib.client.bin import os_dep + + +class GitRepo(object): + """ + This class represents a git repo. + + It is used to pull down a local copy of a git repo, check if the local + repo is up-to-date, if not update. It delegates the install to + implementation classes. + """ + + def __init__(self, repodir, giturl, weburl=None): + if repodir is None: + raise ValueError('You must provide a path that will hold the' + 'git repository') + self.repodir = utils.sh_escape(repodir) + if giturl is None: + raise ValueError('You must provide a git URL repository') + self.giturl = giturl + if weburl is not None: + warnings.warn("Param weburl: You are no longer required to provide " + "a web URL for your git repos", DeprecationWarning) + + # path to .git dir + self.gitpath = utils.sh_escape(os.path.join(self.repodir,'.git')) + + # Find git base command. If not found, this will throw an exception + git_base_cmd = os_dep.command('git') + + # base git command , pointing to gitpath git dir + self.gitcmdbase = '%s --git-dir=%s' % (git_base_cmd, self.gitpath) + + # default to same remote path as local + self._build = os.path.dirname(self.repodir) + + + def _run(self, command, timeout=None, ignore_status=False): + """ + Auxiliary function to run a command, with proper shell escaping. + + @param timeout: Timeout to run the command. + @param ignore_status: Whether we should supress error.CmdError + exceptions if the command did return exit code !=0 (True), or + not supress them (False). + """ + return utils.run(r'%s' % (utils.sh_escape(command)), + timeout, ignore_status) + + + def gitcmd(self, cmd, ignore_status=False): + """ + Wrapper for a git command. + + @param cmd: Git subcommand (ex 'clone'). + @param ignore_status: Whether we should supress error.CmdError + exceptions if the command did return exit code !=0 (True), or + not supress them (False). + """ + cmd = '%s %s' % (self.gitcmdbase, cmd) + return self._run(cmd, ignore_status=ignore_status) + + + def get(self, **kwargs): + """ + This method overrides baseclass get so we can do proper git + clone/pulls, and check for updated versions. The result of + this method will leave an up-to-date version of git repo at + 'giturl' in 'repodir' directory to be used by build/install + methods. + + @param **kwargs: Dictionary of parameters to the method get. + """ + if not self.is_repo_initialized(): + # this is your first time ... + logging.info('Cloning git repo %s', self.giturl) + cmd = 'clone %s %s ' % (self.giturl, self.repodir) + rv = self.gitcmd(cmd, True) + if rv.exit_status != 0: + logging.error(rv.stderr) + raise error.CmdError('Failed to clone git url', rv) + else: + logging.info(rv.stdout) + + else: + # exiting repo, check if we're up-to-date + if self.is_out_of_date(): + logging.info('Updating git repo %s', self.giturl) + rv = self.gitcmd('pull', True) + if rv.exit_status != 0: + logging.error(rv.stderr) + e_msg = 'Failed to pull git repo data' + raise error.CmdError(e_msg, rv) + else: + logging.info('repo up-to-date') + + # remember where the source is + self.source_material = self.repodir + + + def get_local_head(self): + """ + Get the top commit hash of the current local git branch. + + @return: Top commit hash of local git branch + """ + cmd = 'log --pretty=format:"%H" -1' + l_head_cmd = self.gitcmd(cmd) + return l_head_cmd.stdout + + + def get_remote_head(self): + """ + Get the top commit hash of the current remote git branch. + + @return: Top commit hash of remote git branch + """ + cmd1 = 'remote show' + origin_name_cmd = self.gitcmd(cmd1) + cmd2 = 'log --pretty=format:"%H" -1 ' + origin_name_cmd.stdout + r_head_cmd = self.gitcmd(cmd2) + return r_head_cmd.stdout + + + def is_out_of_date(self): + """ + Return whether this branch is out of date with regards to remote branch. + + @return: False, if the branch is outdated, True if it is current. + """ + local_head = self.get_local_head() + remote_head = self.get_remote_head() + + # local is out-of-date, pull + if local_head != remote_head: + return True + + return False + + + def is_repo_initialized(self): + """ + Return whether the git repo was already initialized (has a top commit). + + @return: False, if the repo was initialized, True if it was not. + """ + cmd = 'log --max-count=1' + rv = self.gitcmd(cmd, True) + if rv.exit_status == 0: + return True + + return False + + + def get_revision(self): + """ + Return current HEAD commit id + """ + if not self.is_repo_initialized(): + self.get() + + cmd = 'rev-parse --verify HEAD' + gitlog = self.gitcmd(cmd, True) + if gitlog.exit_status != 0: + logging.error(gitlog.stderr) + raise error.CmdError('Failed to find git sha1 revision', gitlog) + else: + return gitlog.stdout.strip('\n') + + + def checkout(self, remote, local=None): + """ + Check out the git commit id, branch, or tag given by remote. + + Optional give the local branch name as local. + + @param remote: Remote commit hash + @param local: Local commit hash + @note: For git checkout tag git version >= 1.5.0 is required + """ + if not self.is_repo_initialized(): + self.get() + + assert(isinstance(remote, basestring)) + if local: + cmd = 'checkout -b %s %s' % (local, remote) + else: + cmd = 'checkout %s' % (remote) + gitlog = self.gitcmd(cmd, True) + if gitlog.exit_status != 0: + logging.error(gitlog.stderr) + raise error.CmdError('Failed to checkout git branch', gitlog) + else: + logging.info(gitlog.stdout) + + + def get_branch(self, all=False, remote_tracking=False): + """ + Show the branches. + + @param all: List both remote-tracking branches and local branches (True) + or only the local ones (False). + @param remote_tracking: Lists the remote-tracking branches. + """ + if not self.is_repo_initialized(): + self.get() + + cmd = 'branch --no-color' + if all: + cmd = " ".join([cmd, "-a"]) + if remote_tracking: + cmd = " ".join([cmd, "-r"]) + + gitlog = self.gitcmd(cmd, True) + if gitlog.exit_status != 0: + logging.error(gitlog.stderr) + raise error.CmdError('Failed to get git branch', gitlog) + elif all or remote_tracking: + return gitlog.stdout.strip('\n') + else: + branch = [b[2:] for b in gitlog.stdout.split('\n') + if b.startswith('*')][0] + return branch diff --git a/server/git.py b/server/git.py index b974668..0428b80 100644 --- a/server/git.py +++ b/server/git.py @@ -6,63 +6,29 @@ This module defines a class for handling building from git repos """ import os, warnings, logging -from autotest_lib.client.common_lib import error +from autotest_lib.client.common_lib import error, revision_control from autotest_lib.client.bin import os_dep from autotest_lib.server import utils, installable_object -class GitRepo(installable_object.InstallableObject): +class InstallableGitRepo(installable_object.InstallableObject): """ - This class represents a git repo. - - It is used to pull down a local copy of a git repo, check if the local - repo is up-to-date, if not update. It delegates the install to - implementation classes. + This class helps to pick a git repo and install it in a host. """ - def __init__(self, repodir, giturl, weburl=None): - super(installable_object.InstallableObject, self).__init__() - if repodir is None: - raise ValueError('You must provide a path that will hold the' - 'git repository') - self.repodir = utils.sh_escape(repodir) - if giturl is None: - raise ValueError('You must provide a git URL repository') + self.repodir = repodir self.giturl = giturl - if weburl is not None: - warnings.warn("Param weburl: You are no longer required to provide " - "a web URL for your git repos", DeprecationWarning) - - # path to .git dir - self.gitpath = utils.sh_escape(os.path.join(self.repodir,'.git')) - - # Find git base command. If not found, this will throw an exception - git_base_cmd = os_dep.command('git') - - # base git command , pointing to gitpath git dir - self.gitcmdbase = '%s --git-dir=%s' % (git_base_cmd, self.gitpath) - + self.weburl = weburl + self.git_repo = revision_control.GitRepo(self.repodir, self.giturl, + self.weburl) # default to same remote path as local self._build = os.path.dirname(self.repodir) - def _run(self, command, timeout=None, ignore_status=False): - """ - Auxiliary function to run a command, with proper shell escaping. - - @param timeout: Timeout to run the command. - @param ignore_status: Whether we should supress error.CmdError - exceptions if the command did return exit code !=0 (True), or - not supress them (False). - """ - return utils.run(r'%s' % (utils.sh_escape(command)), - timeout, ignore_status) - - # base install method def install(self, host, builddir=None): """ - Install a git repo on a host. It works by pushing the downloaded source + Install a git repo in a host. It works by pushing the downloaded source code to the host. @param host: Host object. @@ -88,8 +54,7 @@ class GitRepo(installable_object.InstallableObject): exceptions if the command did return exit code !=0 (True), or not supress them (False). """ - cmd = '%s %s' % (self.gitcmdbase, cmd) - return self._run(cmd, ignore_status=ignore_status) + return self.git_repo.gitcmd(cmd, ignore_status) def get(self, **kwargs): @@ -102,31 +67,8 @@ class GitRepo(installable_object.InstallableObject): @param **kwargs: Dictionary of parameters to the method get. """ - if not self.is_repo_initialized(): - # this is your first time ... - logging.info('Cloning git repo %s', self.giturl) - cmd = 'clone %s %s ' % (self.giturl, self.repodir) - rv = self.gitcmd(cmd, True) - if rv.exit_status != 0: - print rv.stderr - raise error.CmdError('Failed to clone git url', rv) - else: - print rv.stdout - - else: - # exiting repo, check if we're up-to-date - if self.is_out_of_date(): - logging.info('Updating git repo %s', self.giturl) - rv = self.gitcmd('pull', True) - if rv.exit_status != 0: - print rv.stderr - e_msg = 'Failed to pull git repo data' - raise error.CmdError(e_msg, rv) - else: - print 'repo up-to-date' - - # remember where the source is self.source_material = self.repodir + return self.git_repo.get(**kwargs) def get_local_head(self): @@ -135,9 +77,7 @@ class GitRepo(installable_object.InstallableObject): @return: Top commit hash of local git branch """ - cmd = 'log --pretty=format:"%H" -1' - l_head_cmd = self.gitcmd(cmd) - return l_head_cmd.stdout + return self.git_repo.get_local_head() def get_remote_head(self): @@ -146,11 +86,7 @@ class GitRepo(installable_object.InstallableObject): @return: Top commit hash of remote git branch """ - cmd1 = 'remote show' - origin_name_cmd = self.gitcmd(cmd1) - cmd2 = 'log --pretty=format:"%H" -1 ' + origin_name_cmd.stdout - r_head_cmd = self.gitcmd(cmd2) - return r_head_cmd.stdout + return self.git_repo.get_remote_head() def is_out_of_date(self): @@ -159,14 +95,7 @@ class GitRepo(installable_object.InstallableObject): @return: False, if the branch is outdated, True if it is current. """ - local_head = self.get_local_head() - remote_head = self.get_remote_head() - - # local is out-of-date, pull - if local_head != remote_head: - return True - - return False + return self.git_repo.is_out_of_date() def is_repo_initialized(self): @@ -175,28 +104,14 @@ class GitRepo(installable_object.InstallableObject): @return: False, if the repo was initialized, True if it was not. """ - cmd = 'log --max-count=1' - rv = self.gitcmd(cmd, True) - if rv.exit_status == 0: - return True - - return False + return self.git_repo.is_repo_initialized() def get_revision(self): """ Return current HEAD commit id """ - if not self.is_repo_initialized(): - self.get() - - cmd = 'rev-parse --verify HEAD' - gitlog = self.gitcmd(cmd, True) - if gitlog.exit_status != 0: - print gitlog.stderr - raise error.CmdError('Failed to find git sha1 revision', gitlog) - else: - return gitlog.stdout.strip('\n') + return self.git_repo.get_revision() def checkout(self, remote, local=None): @@ -209,20 +124,7 @@ class GitRepo(installable_object.InstallableObject): @param local: Local commit hash @note: For git checkout tag git version >= 1.5.0 is required """ - if not self.is_repo_initialized(): - self.get() - - assert(isinstance(remote, basestring)) - if local: - cmd = 'checkout -b %s %s' % (local, remote) - else: - cmd = 'checkout %s' % (remote) - gitlog = self.gitcmd(cmd, True) - if gitlog.exit_status != 0: - print gitlog.stderr - raise error.CmdError('Failed to checkout git branch', gitlog) - else: - print gitlog.stdout + return self.git_repo.checkout(remote, local) def get_branch(self, all=False, remote_tracking=False): @@ -233,22 +135,4 @@ class GitRepo(installable_object.InstallableObject): or only the local ones (False). @param remote_tracking: Lists the remote-tracking branches. """ - if not self.is_repo_initialized(): - self.get() - - cmd = 'branch --no-color' - if all: - cmd = " ".join([cmd, "-a"]) - if remote_tracking: - cmd = " ".join([cmd, "-r"]) - - gitlog = self.gitcmd(cmd, True) - if gitlog.exit_status != 0: - print gitlog.stderr - raise error.CmdError('Failed to get git branch', gitlog) - elif all or remote_tracking: - return gitlog.stdout.strip('\n') - else: - branch = [b[2:] for b in gitlog.stdout.split('\n') - if b.startswith('*')][0] - return branch + return self.git_repo.get_branch(all, remote_tracking) diff --git a/server/git_kernel.py b/server/git_kernel.py index 8a3ea8f..015e3c0 100644 --- a/server/git_kernel.py +++ b/server/git_kernel.py @@ -9,9 +9,9 @@ import os, logging import git, source_kernel -class GitKernel(git.GitRepo): +class GitKernel(git.InstallableGitRepo): """ - This class represents a git kernel repo. + This class represents an installable git kernel repo. It is used to pull down a local copy of a git repo, check if the local repo is up-to-date, if not update and then build the kernel from the git repo. @@ -47,6 +47,7 @@ class GitKernel(git.GitRepo): logging.info('Checked out %s on branch %s', self._revision, self._branch) + def show_branch(self): """ Print the current local branch name. -- 1.7.2.2 _______________________________________________ Autotest mailing list [email protected] http://test.kernel.org/cgi-bin/mailman/listinfo/autotest
