Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes for submodule builds (WIP) #236

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
212 changes: 121 additions & 91 deletions scc/git.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
import socket
import yaml
import six
import functools

from ssl import SSLError
from yaclifw.framework import Command, Stop

Expand Down Expand Up @@ -211,6 +213,17 @@ def get_github(login_or_token=None, password=None, **kwargs):
"""
return GHManager(login_or_token, password, **kwargs)


class CD(object):

def __call__(self, f):
@functools.wraps(f)
def wrapper(*args, **kw):
this = args[0]
with this.cd(this.path):
return f(*args, **kw)
return wrapper

#
# Management classes. These allow for proper mocking in tests.
#
Expand Down Expand Up @@ -735,7 +748,7 @@ def is_merged(self):
class GitHubRepository(object):

def __init__(self, gh, user_name, repo_name):
self.log = logging.getLogger("scc.repo")
self.log = logging.getLogger("scc.repo.%s.%s" % (repo_name, user_name))
self.dbg = self.log.debug
self.gh = gh
self.user_name = user_name
Expand Down Expand Up @@ -1007,23 +1020,26 @@ def find_candidate_branches(self, filters,

class GitRepository(object):

def __initlog__(self, name):
self.log = logging.getLogger(name)
self.dbg = self.log.debug
self.info = self.log.info
self.debugWrap = LoggerWrapper(self.log, logging.DEBUG)
self.infoWrap = LoggerWrapper(self.log, logging.INFO)

def __init__(self, gh, path, remote="origin", push_branch=None,
repository_config=None):
"""
Register the git repository path, return the current status and
register the GitHub origin remote.
"""

self.log = logging.getLogger("scc.git")
self.dbg = self.log.debug
self.info = self.log.info
self.debugWrap = LoggerWrapper(self.log, logging.DEBUG)
self.infoWrap = LoggerWrapper(self.log, logging.INFO)

self.gh = gh
self.__initlog__("scc.git")
self.gh = gh # TODO: Possibly needs a cd wrapper
self.path = path
root_path = self.communicate("git", "rev-parse", "--show-toplevel")
self.path = os.path.abspath(root_path.strip())
self.__initlog__("scc.git.%s" % os.path.basename(self.path))

self.get_status()

Expand All @@ -1045,28 +1061,43 @@ def __init__(self, gh, path, remote="origin", push_branch=None,
if gh:
self.origin = gh.gh_repo(repo_name, user_name)

def __str__(self):
return "GitRepo(%s)" % self.path

def register_submodules(self):
if len(self.submodules) == 0:
for directory in self.get_submodule_paths():
repository_config = None
if self.repository_config is not None and \
"submodules" in self.repository_config and \
directory in self.repository_config["submodules"]:
repository_config = \
self.repository_config["submodules"][directory]
try:
with self.cd(self.path):
if len(self.submodules) == 0:
for directory in self.get_submodule_paths():
repository_config = None
if self.repository_config is not None and \
"submodules" in self.repository_config and \
directory in self.repository_config["submodules"]:
repository_config = \
self.repository_config["submodules"][directory]

submodule_repo = \
self.gh.git_repo(directory,
repository_config=repository_config)
self.gh.git_repo(os.path.join(self.path, directory),
repository_config=repository_config)
self.submodules.append(submodule_repo)
submodule_repo.register_submodules()
finally:
self.cd(self.path)

def cd(self, directory):
if not os.path.abspath(os.getcwd()) == os.path.abspath(directory):
self.dbg("cd %s", directory)
os.chdir(directory)
class DirectoryChanger(object):

def __init__(self, dbg):
self.dbg = dbg
self.original = os.getcwd()

def __enter__(self):
pass

def __exit__(self, *args):
os.chdir(self.original)
self.dbg(" < cd: %s" % self.original)

self.dbg(" > cd: %s" % directory)
os.chdir(directory)
return DirectoryChanger(self.dbg)

def communicate(self, *command, **kwargs):
return_stderr = kwargs.pop('return_stderr', False)
Expand All @@ -1081,7 +1112,8 @@ def communicate(self, *command, **kwargs):
rc: %s
stdout: %s
stderr: %s""" % (" ".join(command), p.returncode, o, e)
raise Exception(msg)
self.log.error(msg)
raise Stop("failed command")

if return_stderr:
return o, e
Expand Down Expand Up @@ -1118,9 +1150,8 @@ def wrap_call(self, logWrap, *command, **kwargs):
except Exception:
no_wait = False

self.cd(self.path)
self.dbg("Calling '%s'" % " ".join(command))
p = subprocess.Popen(command, **kwargs)
p = subprocess.Popen(command, cwd=self.path, **kwargs)
if not no_wait:
rc = p.wait()
if rc:
Expand All @@ -1130,20 +1161,21 @@ def wrap_call(self, logWrap, *command, **kwargs):
def write_directories(self):
"""Write directories in candidate PRs comments to a txt file"""

self.cd(self.path)
directories_log = None

for pr in self.origin.candidate_pulls:
directories = pr.parse_comments("test")
if directories:
if directories_log is None:
directories_log = open('directories.txt', 'w')
for directory in directories:
directories_log.write(directory)
directories_log.write("\n")
# Cleanup
if directories_log:
directories_log.close()
# Doesn't use (wrap_)call so changing
with self.cd(self.path):
directories_log = None

for pr in self.origin.candidate_pulls:
directories = pr.parse_comments("test")
if directories:
if directories_log is None:
directories_log = open('directories.txt', 'w')
for directory in directories:
directories_log.write(directory)
directories_log.write("\n")
# Cleanup
if directories_log:
directories_log.close()

#
# General git commands
Expand All @@ -1161,7 +1193,7 @@ def get_current_head(self):
def get_sha1(self, branch):
"""Return the sha1 for the specified branch"""

self.dbg("Get sha1 of %s")
self.dbg("Get sha1 of %s" % branch)
o = self.communicate("git", "rev-parse", branch)
return o.strip()

Expand Down Expand Up @@ -1280,13 +1312,13 @@ def get_rev_list(self, commit):

def has_local_changes(self):
"""Check for local changes in the Git repository"""
try:
self.call("git", "diff-index", "--quiet", "HEAD")
self.dbg("%s has no local changes", self)
return False
except Exception:
out = self.communicate("git", "status", "--porcelain").strip()
if out:
self.dbg("%s has local changes", self)
return True
else:
self.dbg("%s has no local changes", self)
return False

def has_ref(self, ref):
"""Check for reference existence in the local Git repository"""
Expand Down Expand Up @@ -1369,8 +1401,9 @@ def list_remotes(self):
def get_remote_url(self, remote_name="origin"):
"""Return the URL of the remote"""

self.cd(self.path)
return git_config("remote.%s.url" % remote_name)
# Protect git_config
with self.cd(self.path):
return git_config("remote.%s.url" % remote_name)

#
# Higher level git commands
Expand Down Expand Up @@ -1741,11 +1774,13 @@ def get_fork_filter(self, is_submodule=False):
return lambda x: (
'/' in x and not x.endswith(repo_names))

@CD()
def rmerge(self, filters, info=False, comment=False, commit_id="merge",
top_message=None, update_gitmodules=False,
set_commit_status=False, allow_empty=True, is_submodule=False):
"""Recursively merge PRs for each submodule."""

self.dbg("rmerge: %s" % filters)
if self.repository_config is not None and \
"base-branch" in self.repository_config and \
filters["base"] != self.repository_config["base-branch"]:
Expand All @@ -1763,8 +1798,7 @@ def rmerge(self, filters, info=False, comment=False, commit_id="merge",
if info:
merge_msg += self.origin.merge_info()
else:
self.cd(self.path)
self.write_directories()
self.write_directories() # Handles own chdir
presha1 = self.get_current_sha1()
if self.has_remote_branch(filters["base"], self.remote):
ff_msg, ff_log = self.fast_forward(filters["base"],
Expand Down Expand Up @@ -1792,15 +1826,12 @@ def rmerge(self, filters, info=False, comment=False, commit_id="merge",
# Do not copy top-level PRs
for ftype in ["include", "exclude"]:
sub_filters[ftype].pop("pr", None)
try:
submodule_updated, submodule_msg = submodule_repo.rmerge(
sub_filters, info, comment, commit_id=commit_id,
update_gitmodules=update_gitmodules,
set_commit_status=set_commit_status,
allow_empty=allow_empty, is_submodule=True)
merge_msg += "\n" + submodule_msg
finally:
self.cd(self.path)
submodule_updated, submodule_msg = submodule_repo.rmerge(
sub_filters, info, comment, commit_id=commit_id,
update_gitmodules=update_gitmodules,
set_commit_status=set_commit_status,
allow_empty=allow_empty, is_submodule=True)
merge_msg += "\n" + submodule_msg

if not info:
summary_update = self.summary_commit(
Expand Down Expand Up @@ -1828,29 +1859,31 @@ def summary_commit(self, merge_msg, commit_id="merge", top_message=None,
% (top_message, merge_msg + merge_msg_footer)

if update_gitmodules:
submodule_paths = self.get_submodule_paths()
for path in submodule_paths:
# Read submodule URL registered in .gitmodules
config_url = "submodule.%s.url" % path
submodule_url = git_config(config_url,
config_file=".gitmodules")

# Substitute submodule URL using connection login
user = self.gh.get_login()
pattern = '(.*github.com[:/]).*(/.*.git)'
new_url = re.sub(pattern, r'\1%s\2' % user, submodule_url)
git_config(config_url, config_file=".gitmodules",
value=new_url)

# Substitute submodule branch
if self.push_branch is not None:
config_branch = "submodule.%s.branch" % path
git_config(config_branch, config_file=".gitmodules",
value=self.push_branch_name)
# Protect calls to git_config with self.cd
with self.cd(self.path):
submodule_paths = self.get_submodule_paths()
for path in submodule_paths:
# Read submodule URL registered in .gitmodules
config_url = "submodule.%s.url" % path
submodule_url = git_config(config_url,
config_file=".gitmodules")

# Substitute submodule URL using connection login
user = self.gh.get_login()
pattern = '(.*github.com[:/]).*(/.*.git)'
new_url = re.sub(pattern, r'\1%s\2' % user, submodule_url)
git_config(config_url, config_file=".gitmodules",
value=new_url)

# Substitute submodule branch
if self.push_branch is not None:
config_branch = "submodule.%s.branch" % path
git_config(config_branch, config_file=".gitmodules",
value=self.push_branch_name)

updated = self.has_local_changes()
if updated:
self.call("git", "commit", "-a", "-n", "-m", commit_message)
self.communicate("git", "commit", "-a", "-n", "-m", commit_message)
elif allow_empty:
self.call("git", "commit", "--allow-empty", '-a', "-n", "-m",
commit_message)
Expand Down Expand Up @@ -1940,7 +1973,6 @@ def rcleanup(self):
submodule_repo.rcleanup()
except Exception:
self.dbg("Failed to clean repository %s" % self.path)
self.cd(self.path)

def cleanup(self):
"""Remove remote branches created for merging."""
Expand All @@ -1962,10 +1994,7 @@ def rpush(self, branch_name, remote, force=False):
self.dbg("Pushed %s to %s" % (branch_name, full_remote))

for submodule_repo in self.submodules:
try:
submodule_repo.rpush(branch_name, remote, force=force)
finally:
self.cd(self.path)
submodule_repo.rpush(branch_name, remote, force=force)

def __del__(self):
# We need to make sure our logging wrappers are closed when this
Expand Down Expand Up @@ -3171,12 +3200,13 @@ def merge(self, args, main_repo):
if args.check_commit_status:
commit_args.append("-S%s" % args.check_commit_status)

updated, merge_msg = main_repo.rmerge(
self.filters, args.info,
args.comment, commit_id=" ".join(commit_args),
top_message=args.message,
update_gitmodules=args.update_gitmodules,
set_commit_status=args.set_commit_status)
with main_repo.cd(main_repo.path):
updated, merge_msg = main_repo.rmerge(
self.filters, args.info,
args.comment, commit_id=" ".join(commit_args),
top_message=args.message,
update_gitmodules=args.update_gitmodules,
set_commit_status=args.set_commit_status)

for line in merge_msg.split("\n"):
self.log.info(line)
Expand Down