# Copyright 2016 Peter Dahlberg # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import os import errno import shlex from collections import namedtuple, deque from subprocess import Popen, PIPE CmdResult = namedtuple("CmdResult", "cmd status stdout stderr") class GitCommandError(Exception): def __init__(self, message, result, *args, **kwargs): super().__init__(message, result, *args, **kwargs) self.result = result class Repo: def __init__(self, repo_dir, git_cmd="git", default_timeout=None): if not os.path.exists(repo_dir): raise FileNotFoundError(errno.ENOENT, "Path, does not exist", repo_dir) self.repo_dir = repo_dir self.git_cmd = git_cmd self.default_timeout = default_timeout def __getattr__(self, name): name = name.replace("_", "-") def cmdcaller(*args, cmd=None, **kwargs): cmdargs = shlex.split(cmd) if cmd else args return self.cmd(*((self.git_cmd, name) + tuple(cmdargs)), **kwargs) return cmdcaller def cmd(self, *args, timeout=None, env=None, universal_newlines=True, errors_raise=True): timeout = timeout if timeout else self.default_timeout proc = self.popen_cmd(*args, env=env) outs, errs = proc.communicate(timeout=timeout) status = proc.wait() res = CmdResult(args,status, outs, errs) if status != 0 and errors_raise: raise GitCommandError("git failed: {}".format(args), res) return res def popen_cmd(self, *args, env=None, universal_newlines=True): return Popen(args, stdout=PIPE, stderr=PIPE, cwd=self.repo_dir, env=env, universal_newlines=universal_newlines, start_new_session=True) def is_bare(self): result = self.rev_parse("--is-bare-repository") return result.stdout.startswith("true") def is_repo(self): return self.rev_parse("--git-dir", errors_raise=False).status == 0 def config_get(self, key): res = self.config("--get", key) return res.stdout.rstrip("\r\n") def submodule_sync_update_init_recursive_force(self): results = [] todo = deque() todo.extend(self._get_submod_paths()) while todo: curr = todo.popleft() if not os.path.exists(os.path.join(self.repo_dir, curr)): continue # that happens, strangely... results.append(self.cmd(self.git_cmd, "-C", curr, "submodule", "sync")) results.append(self.cmd(self.git_cmd, "-C", curr, "submodule", "update", "--init", "--force")) todo.extend(os.path.join(curr, p) for p in self._get_submod_paths(curr)) return results def _get_submod_paths(self, submod="."): if not os.path.exists(os.path.join(self.repo_dir, submod, ".gitmodules")): return () result = self.cmd(self.git_cmd, "-C", submod, "config", "--file", ".gitmodules", "--get-regexp", "submodule\..*\.path") return tuple(p.split(maxsplit=1)[1] for p in result.stdout.splitlines()) def log_git_result(result, out_logger=None, err_logger=None, status_logger=None): if status_logger: err_logger('%s exit status: %s', result.cmd, result.status) if out_logger: out_logger('git stdout:\n%s', result.stdout) if err_logger: err_logger('git stderr:\n%s', result.stderr)