#!/usr/bin/env python # Copyright (C) 2013 Linaro Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see import argparse import logging import os import re import subprocess from logging.handlers import TimedRotatingFileHandler from tempfile import gettempdir # Match a directory that ends with .git, but not only .git GIT_DIRECTORY_ENDS = re.compile(".+\.git$") # Name for a lock file. LOCK_FILE_NAME = "update-repos.lock" LOCK_FILE = os.path.join(gettempdir(), LOCK_FILE_NAME) FILE_NAME = os.path.basename(__file__) # Default log directory and log file. DEFAULT_LOG_DIR = "/var/log/rhodecode" LOG_FILE_NAME = FILE_NAME + ".log" # When to rotate logs. DEFAULT_ROTATING_TIME = 'midnight' # How many old logs to keep. KEEP_MAX_LOGS = 10 # Default logger. logger = logging.getLogger(FILE_NAME) def args_parser(): """Sets up the argument parser.""" parser = argparse.ArgumentParser() parser.add_argument("--repos-dir", required=True, help="The directory where repositories are stored.") parser.add_argument("--user", help="User to run the commands as.") parser.add_argument("--log-dir", default=DEFAULT_LOG_DIR, help="Directory to store logs. Defaults to '%s'." % DEFAULT_LOG_DIR) parser.add_argument("--debug", action="store_true", help="Print debugging statements.") return parser def setup_logging(debug, log_dir): """Sets up logging. :param debug: If the level should be set to DEBUG. :type bool :param log_dir: Where to store file based logs. """ th_formatter = "%(asctime)s %(levelname)-8s %(message)s" log_file = os.path.join(log_dir, LOG_FILE_NAME) timed_handler = TimedRotatingFileHandler(log_file, when=DEFAULT_ROTATING_TIME, backupCount=KEEP_MAX_LOGS) timed_handler.setFormatter(logging.Formatter(th_formatter)) if debug: logger.setLevel(logging.DEBUG) timed_handler.setLevel(logging.DEBUG) else: logger.setLevel(logging.INFO) timed_handler.setLevel(logging.INFO) logger.addHandler(timed_handler) def fetch_updates(repos_dir, user): """Traverse the file system and fetch updates. :param repos_dir: The directory holding repositories. :param user: The use to run the commands as. """ for root, dirs, files in os.walk(os.path.abspath(repos_dir)): if GIT_DIRECTORY_ENDS.match(root): if files: # We really are in a git repository. cmd_args = [] if user: cmd_args = ["sudo", "-u", user, "-H"] cmd_args += ["git", "fetch", "--all", "-q"] repo_name = os.path.basename(root) process = subprocess.Popen(cmd_args, cwd=root, stdout=subprocess.PIPE, stderr=subprocess.PIPE) logger.info("Fetching updates for '%s' repository." % repo_name) p_out, p_err = process.communicate() if process.returncode != 0: logger.error("Error fetching updates for repository " "'%s'." % repo_name) logger.debug(p_err) else: # git repositories always have a HEAD file, or it means they # are empty. logger.debug("Skipping directory '%s': valid git one, but " "looks empty." % root) continue else: logger.debug("Skipped directory '%s', not matching a git " "one." % root) if __name__ == '__main__': parser = args_parser() args = parser.parse_args() if os.path.exists(LOCK_FILE): print "Another process is still running: cannot acquire lock." else: setup_logging(args.debug, args.log_dir) try: with open(LOCK_FILE, 'w'): fetch_updates(args.repos_dir, args.user) finally: os.unlink(LOCK_FILE)