def to_log_level(self, level):
        """Debugging dump."""

        # Single line dump
        fmt = NTR('Row: {sha1:<7} {mode:<6} {p4_request:<6} {p4filetype:<10}'
                  ' {gwt_path:<10} {depot_path:<10}')

        topline = fmt.format(
                           sha1       = p4gf_util.abbrev(self.sha1) \
                                        if self.sha1 else '0000000'
                         , mode       = p4gf_util.quiet_none(
                                        p4gf_util.mode_str(  self.mode))
                         , gwt_path   = self.gwt_path
                         , depot_path = self.depot_path
                         , p4_request = p4gf_util.quiet_none(self.p4_request)
                         , p4filetype = p4gf_util.quiet_none(self.p4filetype)
                         )

        # Detail each cell at DEBUG2 not DEBUG3. DEBUG2 produces one-
        # line dumps for each cell, which should be useful. DEBUG3 will
        # produce multi-line dumps of each cell, which is VERY noisy.
        if level <= logging.DEBUG2:
            # Multi-line dump.
            lines = [topline]
            for i, cell in enumerate(self.cells):
                if not cell:
                    lines.append(NTR('  {i}: {cell}').format(i=i, cell=cell))
                else:
                    lines.append(
                        NTR('  {i}: {cell}').format(
                            i=i, cell=cell.to_log_level(level)))
            return '\n'.join(lines)
        else:
            return topline
    def to_log_level(self, level):
        '''Debugging dump.'''

        # Single line dump
        fmt = NTR('Row: {sha1:<7} {mode:<6} {p4_request:<6} {p4filetype:<10}'
               ' {gwt_path:<10} {depot_path:<10}')

        topline = fmt.format(
                           sha1       = p4gf_util.abbrev(self.sha1) \
                                        if self.sha1 else '0000000'
                         , mode       = p4gf_util.quiet_none(
                                        p4gf_util.mode_str(  self.mode))
                         , gwt_path   = self.gwt_path
                         , depot_path = self.depot_path
                         , p4_request = p4gf_util.quiet_none(self.p4_request)
                         , p4filetype = p4gf_util.quiet_none(self.p4filetype)
                         )

                # Detail each cell at DEBUG2 not DEBUG3. DEBUG2 produces one-
                # line dumps for each cell, which should be useful. DEBUG3 will
                # produce multi-line dumps of each cell, which is VERY noisy.
        if level <= logging.DEBUG2:
            # Multi-line dump.
            lines = [ topline ]
            for i, cell in enumerate(self.cells):
                if not cell:
                    lines.append(NTR('  {i}: {cell}').format(i=i, cell=cell))
                else:
                    lines.append(NTR('  {i}: {cell}')
                            .format( i=i
                                   , cell=cell.to_log_level(level)))
            return '\n'.join(lines)
        else:
            return topline
Exemplo n.º 3
0
def _test_dump_result_to_stdout(assigner):
    '''
    Dump all assignments to stdout in a format that a test script would enjoy.
    '''
    #print("Commit count: {}".format(len(assigner.rev_list)))
    fmt = NTR("{sha1:<7.7}\t{branch_id}\t{subject}")
    for rev in assigner.rev_list:
        p = p4gf_proc.popen(['git', 'log', '-1', '--pretty=format:%s', rev])
        subject = p['out'].splitlines()[0]
        branch_id = assigner.assign_dict[rev].branch_id_str()
        print(fmt.format(sha1=rev, branch_id=branch_id, subject=subject))
def _dict_to_string(d):
    """Convert as_dict()'s result into a multiline string suitable for user display."""
    if 'release-codeline' in d:
        template = NTR("""{company}
{copyright}
Rev. {product_abbrev}/{release_year}.{release_sub}.{release_codeline}/{patchlevel} ({date_year}/{date_month}/{date_day}).
SHA1: {bin_sha1}
Git: {git}
Python: {python}
P4Python: {p4python}
""")
    else:
        template = NTR("""{company}
{copyright}
Rev. {product_abbrev}/{release_year}.{release_sub}/{patchlevel} ({date_year}/{date_month}/{date_day}).
SHA1: {bin_sha1}
Git: {git}
Python: {python}
P4Python: {p4python}
""")
    return template.format(**d)
Exemplo n.º 5
0
def _dict_to_string(d):
    """Convert as_dict()'s result into a multiline string suitable for user display."""

    if 'release-codeline' in d:
        template = NTR("""{company}
{copyright}
Rev. {product_abbrev}/{release_year}.{release_sub}.{release_codeline}/{patchlevel} ({date_year}/{date_month}/{date_day}).
SHA1: {bin_sha1}
Git: {git}
Python: {python}
P4Python: {p4python}
""")
    else:
        template = NTR("""{company}
{copyright}
Rev. {product_abbrev}/{release_year}.{release_sub}/{patchlevel} ({date_year}/{date_month}/{date_day}).
SHA1: {bin_sha1}
Git: {git}
Python: {python}
P4Python: {p4python}
""")
    return template.format(**d)
Exemplo n.º 6
0
def main(poll_only=False):
    """set up repo for a view
       view_name_git    is the untranslated repo name
       view_name        is the translated repo name
    """
    p4gf_proc.install_stack_dumper()
    _log_environ(os.environ)
    with p4gf_server_common.ExceptionAuditLogger()\
    , p4gf_create_p4.Closer():
        LOG.debug(p4gf_log.memory_usage())
        start_time = time.time()
        args = parse_args(sys.argv[1:])
        if not args:
            return 1

        is_push = 'upload' not in args.command[0]

        # Record the p4 user in environment. We use environment to pass to
        # git-invoked hook. We don't have to set ctx.authenticated_p4user because
        # Context.__init__() reads it from environment, which we set here.
        os.environ[p4gf_const.P4GF_AUTH_P4USER] = args.user

        # view_name_git    is the untranslated repo name
        # view_name        is the translated repo name

        # print "args={}".format(args)
        view_name_git = args.options[-1]
        # translate '/' ':' ' '  .. etc .. for internal view_name
        view_name = p4gf_translate.TranslateReponame.git_to_repo(view_name_git)
        LOG.debug("public view_name: {0}   internal view_name: {1}".
                format(view_name_git, view_name))


        p4gf_util.reset_git_enviro()
        p4 = p4gf_create_p4.create_p4()
        if not p4:
            return 2
        LOG.debug("connected to P4: %s", p4)

        p4gf_server_common.check_readiness(p4)

        p4gf_server_common.check_lock_perm(p4)

        if not p4gf_server_common.check_protects(p4):
            p4gf_server_common.raise_p4gf_perm()

        if p4gf_server_common.run_special_command(view_name, p4, args.user):
            return 0

        # Initialize the external process launcher early, before allocating lots
        # of memory, and just after all other conditions have been checked.
        p4gf_proc.init()
        # Prepare for possible spawn of GitMirror worker process by forking
        # now before allocating lots of memory.
        p4gf_gitmirror.setup_spawn(view_name)
        # Kick off garbage collection debugging, if enabled.
        p4gf_gc.init_gc()

        if poll_only:
            view_perm = None
        else:
            # Go no further, create NOTHING, if user not authorized.
            # We use the translated internal view name here for perm authorization
            required_perm = p4gf_server_common.COMMAND_TO_PERM[args.command[0]]
            view_perm = p4gf_group.ViewPerm.for_user_and_view(p4, args.user,
                        view_name, required_perm)
            p4gf_server_common.check_authorization(p4, view_perm, args.user, args.command[0],
                                                   view_name)

        # Create Git Fusion server depot, user, config. NOPs if already created.
        p4gf_init.init(p4)

        write_motd()

        # view_name is the internal view_name (identical when notExist special chars)
        before_lock_time = time.time()
        with p4gf_lock.view_lock(p4, view_name) as view_lock:
            after_lock_time = time.time()

            # Create Git Fusion per-repo client view mapping and config.
            #
            # NOPs if already created.
            # Create the empty directory that will hold the git repo.
            init_repo_status = p4gf_init_repo.init_repo(p4, view_name, view_lock)
            if init_repo_status == p4gf_init_repo.INIT_REPO_OK:
                repo_created = True
            elif init_repo_status == p4gf_init_repo.INIT_REPO_EXISTS:
                repo_created = False
            else:
                return 1

            # If authorization came from default, not explicit group
            # membership, copy that authorization to a group now. Could
            # not do this until after p4gf_init_repo() has a chance to
            # create not-yet-existing groups.
            if view_perm:
                view_perm.write_if(p4)

            # Now that we have valid git-fusion-user and
            # git-fusion-<view> client, replace our temporary P4
            # connection with a more permanent Context, shared for the
            # remainder of this process.
            with p4gf_context.create_context(view_name, view_lock) as ctx:
                LOG.debug("reconnected to P4, p4gf=%s", ctx.p4gf)

                # Find directory paths to feed to git.
                ctx.log_context()

                # cd into the work directory. Not all git functions react well
                # to --work-tree=xxxx.
                cwd = os.getcwd()
                os.chdir(ctx.view_dirs.GIT_WORK_TREE)

                # Only copy from Perforce to Git if no other process is cloning
                # from this Git repo right now.
                shared_in_progress = p4gf_lock.shared_host_view_lock_exists(ctx.p4, view_name)
                if not shared_in_progress:
                    # Copy any recent changes from Perforce to Git.
                    try:
                        LOG.debug("bare: No git-upload-pack in progress, force non-bare"
                                  " before update Git from Perforce.")
                        p4gf_git.set_bare(False)
                        p4gf_copy_p2g.copy_p2g_ctx(ctx)
                        p4gf_init_repo.process_imports(ctx)

                        # Now is also an appropriate time to clear out any stale Git
                        # Swarm reviews. We're pre-pull, pre-push, time when we've
                        # got exclusive write access to the Git repo,
                        GSReviewCollection.delete_refs_for_closed_reviews(ctx)

                    except p4gf_lock.LockCanceled as lc:
                        LOG.warning(str(lc))
                    except:
                        # Dump failure to log, BEFORE cleanup, just in case
                        # cleanup ALSO fails and throws its own error (which
                        # happens if we're out of memory).
                        LOG.error(traceback.format_exc())

                        if repo_created:
                            # Return to the original working directory to allow the
                            # config code to call os.getcwd() without dying, since
                            # we are about to delete the current working directory.
                            os.chdir(cwd)
                            p4gf_server_common.cleanup_client(ctx, view_name)
                        raise

                if poll_only:
                    code = os.EX_OK
                else:

                    git_caller = functools.partial(_call_git, args, ctx)
                    try:

                        # Deep in call_git(), we grab an 'p4 reviews' lock on
                        # ctx.clientmap's LHS. Switch that clientmap to our
                        # full union view to prevent simultaneous 'git push'es
                        # from clobbering each other in some shared depot
                        # branch. Must include all lightweight branches, too.
                        ctx.switch_client_view_to_union()

                        exclusive = 'upload' not in args.command[0]
                        code = p4gf_call_git.call_git(
                                git_caller, ctx, view_name, view_lock, exclusive)
                        if is_push:
                            GSReviewCollection.post_push(ctx)
                    except p4gf_atomic_lock.LockConflict as lc:
                        sys.stderr.write("{}\n".format(lc))
                        code = os.EX_SOFTWARE

            p4gf_gc.process_garbage(NTR('at end of auth_server'))
            if LOG.isEnabledFor(logging.DEBUG):
                end_time = time.time()
                frm = NTR("Runtime: preparation {} ms, lock acquisition {} ms,"
                          " processing {} ms")
                LOG.debug(frm.format(before_lock_time - start_time,
                                    after_lock_time - before_lock_time,
                                    end_time - after_lock_time))
        return code
Exemplo n.º 7
0
def _wsgi_app(environ, start_response):
    """
    WSGI application to process the incoming Git client request. This is
    nearly equivalent to p4gf_auth_server.main() with the exception of
    input validation and error handling.
    """
    p4gf_log.record_http(environ)
    p4gf_version.log_version()
    _log_environ(environ)
    p4gf_version.version_check()
    LOG.debug("processing HTTP request, pid={}".format(os.getpid()))
    # Keep the content type to exactly 'text/plain' so there is at least
    # the remote chance that Git might show our error messages (does not
    # appear to work in practice, however).
    headers = [('Content-Type', 'text/plain')]

    encoding = sys.getfilesystemencoding()
    if encoding == 'ascii':
        # This encoding is wrong and will eventually lead to problems.
        LOG.error("Using 'ascii' file encoding will ultimately result in errors, "
            "please set LANG/LC_ALL to 'utf-8' in web server configuration.")
        start_response(_('500 Internal Server Error'), headers)
        return [b"Filesystem encoding not set to acceptable value.\n"]

    # Sanity check the request.
    for (name, status, msg) in _REQUIRED_HTTP_PARAMS:
        if name not in environ:
            start_response(status, headers)
            return [msg.encode('UTF-8')]

    input_name = environ['wsgi.input']
    # Extract the view_name_git by removing the expected git request suffixes
    path_info = environ['PATH_INFO']
    git_suffixes = ['/info/refs', '/HEAD', '/git-upload-pack', '/git-receive-pack']
    path_end = len(path_info)
    for suffix in git_suffixes:
        try:
            path_end = path_info.index(suffix)
            break
        except ValueError:
            pass
    # slice away the leading slash and the trailing git request suffixes
    view_name_git  = path_info[1:path_end]
    # and remove the view_name_git from the front of PATH_INFO
    environ['PATH_INFO'] = path_info[path_end:]
    LOG.debug("new PATH_INFO {0} view_name_git {1}".format(environ['PATH_INFO'], view_name_git))

    if not view_name_git:
        start_response(_('400 Bad Request'), headers)
        msg = _('Missing required repository name in URL\n')
        return [msg.encode('UTF-8')]
    # translate '/' ':' ' ' .. etc .. for internal view_name
    view_name = p4gf_translate.TranslateReponame.git_to_repo(view_name_git)
    LOG.debug("public view_name: {0}   internal view_name: {1}".format(view_name_git, view_name))

    audit_logger = p4gf_server_common.ExceptionAuditLogger()
    p4_closer = p4gf_create_p4.Closer()
    sink = OutputSink()
    temp_deleter = deleting(input_name)
    mirror_closer = unmirror(view_name)
    with audit_logger   \
        , p4_closer     \
        , sink          \
        , temp_deleter  \
        , mirror_closer:
        LOG.debug(p4gf_log.memory_usage())
        start_time = time.time()

        p4gf_util.reset_git_enviro()
        p4 = p4gf_create_p4.create_p4()
        if not p4:
            start_response(_('500 Internal Server Error'), headers)
            return [b"Perforce connection failed\n"]
        LOG.debug("connected to P4: %s", p4)

        p4gf_server_common.check_readiness(p4)
        p4gf_server_common.check_lock_perm(p4)
        if not p4gf_server_common.check_protects(p4):
            p4gf_server_common.raise_p4gf_perm()

        user = environ['REMOTE_USER']
        if p4gf_server_common.run_special_command(view_name, p4, user):
            start_response(_('200 OK'), headers)
            return [sink.readall()]
        command = _get_command(environ)
        if not command:
            start_response(_('400 Bad Request'), headers)
            return [b"Unrecognized service\n"]
        # Other places in the Perforce-to-Git phase will need to know the
        # name of client user, so set that here. As for Git-to-Perforce,
        # that is handled later by setting the REMOTE_USER envar. Notice
        # also that we're setting os.environ and not 'environ'.
        os.environ[p4gf_const.P4GF_AUTH_P4USER] = user
        # Likewise, some code needs a hint that the request is coming over
        # one protocol (HTTP) or the other (SSH).
        os.environ['REMOTE_ADDR'] = environ['REMOTE_ADDR']

        # Initialize the external process launcher early, before allocating lots
        # of memory, and just after all other conditions have been checked.
        p4gf_proc.init()
        # Prepare for possible spawn of GitMirror worker process by forking
        # now before allocating lots of memory.
        p4gf_gitmirror.setup_spawn(view_name)
        # Kick off garbage collection debugging, if enabled.
        p4gf_gc.init_gc()

        # Go no further, create NOTHING, if user not authorized.
        # We use the translated internal view name here for perm authorization
        required_perm = p4gf_server_common.COMMAND_TO_PERM[command]
        view_perm = p4gf_group.ViewPerm.for_user_and_view(p4, user, view_name, required_perm)
        try:
            p4gf_server_common.check_authorization(p4, view_perm, user, command, view_name)
        except p4gf_server_common.CommandError as ce:
            start_response(_('403 Forbidden'), headers)
            return [str(ce).encode('UTF-8')]

        # Create Git Fusion server depot, user, config. NOPs if already created.
        p4gf_init.init(p4)

        before_lock_time = time.time()
        with p4gf_lock.view_lock(p4, view_name) as view_lock:
            after_lock_time = time.time()

            # Create Git Fusion per-repo client view mapping and config.
            init_repo_status = p4gf_init_repo.init_repo(p4, view_name, view_lock)
            if init_repo_status == p4gf_init_repo.INIT_REPO_OK:
                repo_created = True
            elif init_repo_status == p4gf_init_repo.INIT_REPO_EXISTS:
                repo_created = False
            elif init_repo_status == p4gf_init_repo.INIT_REPO_NOVIEW:
                start_response(_('404 Not Found'), headers)
                return [sink.readall()]
            else:
                start_response(_('500 Internal Server Error'), headers)
                return [b"Repository initialization failed\n"]

            # If authorization came from default, not explicit group
            # membership, copy that authorization to a group now. Could
            # not do this until after p4gf_init_repo() has a chance to
            # create not-yet-existing groups.
            if view_perm:
                view_perm.write_if(p4)

            # Now that we have valid git-fusion-user and
            # git-fusion-<view> client, replace our temporary P4
            # connection with a more permanent Context, shared for the
            # remainder of this process.
            with p4gf_context.create_context(view_name, view_lock) as ctx:
                LOG.debug("reconnected to P4, p4gf=%s", ctx.p4gf)
                ctx.log_context()

                # cd into the work directory. Not all git functions react well
                # to --work-tree=xxxx.
                cwd = os.getcwd()
                os.chdir(ctx.view_dirs.GIT_WORK_TREE)

                # Only copy from Perforce to Git if no other process is cloning
                # from this Git repo right now.
                shared_in_progress = p4gf_lock.shared_host_view_lock_exists(ctx.p4, view_name)
                if not shared_in_progress:
                    # Copy any recent changes from Perforce to Git.
                    try:
                        LOG.debug("bare: No git-upload-pack in progress, force non-bare"
                                  " before update Git from Perforce.")
                        p4gf_git.set_bare(False)
                        p4gf_copy_p2g.copy_p2g_ctx(ctx)
                        p4gf_init_repo.process_imports(ctx)

                        # Now is also an appropriate time to clear out any stale Git
                        # Swarm reviews. We're pre-pull, pre-push, time when we've
                        # got exclusive write access to the Git repo,
                        GSReviewCollection.delete_refs_for_closed_reviews(ctx)

                    except p4gf_lock.LockCanceled as lc:
                        LOG.warning(str(lc))
                    except:
                        # Dump failure to log, BEFORE cleanup, just in case
                        # cleanup ALSO fails and throws its own error (which
                        # happens if we're out of memory).
                        LOG.error(traceback.format_exc())

                        if repo_created:
                            # Return to the original working directory to allow the
                            # config code to call os.getcwd() without dying, since
                            # we are about to delete the current working directory.
                            os.chdir(cwd)
                            p4gf_server_common.cleanup_client(ctx, view_name)
                        raise

                try:
                    exclusive = 'upload' not in command
                    is_push   = 'upload' not in command
                    git_caller = functools.partial(_call_git, input_name, environ, ctx)
                    p4gf_call_git.call_git(git_caller, ctx, view_name, view_lock, exclusive)
                    if is_push:
                        GSReviewCollection.post_push(ctx)
                except p4gf_atomic_lock.LockConflict as lc:
                    start_response(_('500 Internal Server Error'), headers)
                    return ["{}".format(lc).encode('UTF-8')]

        p4gf_gc.process_garbage('at end of auth_server')
        if LOG.isEnabledFor(logging.DEBUG):
            end_time = time.time()
            frm = NTR('Runtime: preparation {} ms, lock acquisition {} ms, processing {} ms')
            LOG.debug(frm.format(before_lock_time - start_time,
                                after_lock_time - before_lock_time,
                                end_time - after_lock_time))
        return []
Exemplo n.º 8
0
def main(poll_only=False):
    """set up repo for a view
       view_name_git    is the untranslated repo name
       view_name        is the translated repo name
    """
    p4gf_proc.install_stack_dumper()
    _log_environ(os.environ)
    with p4gf_server_common.ExceptionAuditLogger()\
    , p4gf_create_p4.Closer():
        LOG.debug(p4gf_log.memory_usage())
        start_time = time.time()
        args = parse_args(sys.argv[1:])
        if not args:
            return 1

        is_push = 'upload' not in args.command[0]

        # Record the p4 user in environment. We use environment to pass to
        # git-invoked hook. We don't have to set ctx.authenticated_p4user because
        # Context.__init__() reads it from environment, which we set here.
        os.environ[p4gf_const.P4GF_AUTH_P4USER] = args.user

        # view_name_git    is the untranslated repo name
        # view_name        is the translated repo name

        # print "args={}".format(args)
        view_name_git = args.options[-1]
        # translate '/' ':' ' '  .. etc .. for internal view_name
        view_name = p4gf_translate.TranslateReponame.git_to_repo(view_name_git)
        LOG.debug("public view_name: {0}   internal view_name: {1}".format(
            view_name_git, view_name))

        p4gf_util.reset_git_enviro()
        p4 = p4gf_create_p4.create_p4()
        if not p4:
            return 2
        LOG.debug("connected to P4: %s", p4)

        p4gf_server_common.check_readiness(p4)

        p4gf_server_common.check_lock_perm(p4)

        if not p4gf_server_common.check_protects(p4):
            p4gf_server_common.raise_p4gf_perm()

        if p4gf_server_common.run_special_command(view_name, p4, args.user):
            return 0

        # Initialize the external process launcher early, before allocating lots
        # of memory, and just after all other conditions have been checked.
        p4gf_proc.init()
        # Prepare for possible spawn of GitMirror worker process by forking
        # now before allocating lots of memory.
        p4gf_gitmirror.setup_spawn(view_name)
        # Kick off garbage collection debugging, if enabled.
        p4gf_gc.init_gc()

        if poll_only:
            view_perm = None
        else:
            # Go no further, create NOTHING, if user not authorized.
            # We use the translated internal view name here for perm authorization
            required_perm = p4gf_server_common.COMMAND_TO_PERM[args.command[0]]
            view_perm = p4gf_group.ViewPerm.for_user_and_view(
                p4, args.user, view_name, required_perm)
            p4gf_server_common.check_authorization(p4, view_perm, args.user,
                                                   args.command[0], view_name)

        # Create Git Fusion server depot, user, config. NOPs if already created.
        p4gf_init.init(p4)

        write_motd()

        # view_name is the internal view_name (identical when notExist special chars)
        before_lock_time = time.time()
        with p4gf_lock.view_lock(p4, view_name) as view_lock:
            after_lock_time = time.time()

            # Create Git Fusion per-repo client view mapping and config.
            #
            # NOPs if already created.
            # Create the empty directory that will hold the git repo.
            init_repo_status = p4gf_init_repo.init_repo(
                p4, view_name, view_lock)
            if init_repo_status == p4gf_init_repo.INIT_REPO_OK:
                repo_created = True
            elif init_repo_status == p4gf_init_repo.INIT_REPO_EXISTS:
                repo_created = False
            else:
                return 1

            # If authorization came from default, not explicit group
            # membership, copy that authorization to a group now. Could
            # not do this until after p4gf_init_repo() has a chance to
            # create not-yet-existing groups.
            if view_perm:
                view_perm.write_if(p4)

            # Now that we have valid git-fusion-user and
            # git-fusion-<view> client, replace our temporary P4
            # connection with a more permanent Context, shared for the
            # remainder of this process.
            with p4gf_context.create_context(view_name, view_lock) as ctx:
                LOG.debug("reconnected to P4, p4gf=%s", ctx.p4gf)

                # Find directory paths to feed to git.
                ctx.log_context()

                # cd into the work directory. Not all git functions react well
                # to --work-tree=xxxx.
                cwd = os.getcwd()
                os.chdir(ctx.view_dirs.GIT_WORK_TREE)

                # Only copy from Perforce to Git if no other process is cloning
                # from this Git repo right now.
                shared_in_progress = p4gf_lock.shared_host_view_lock_exists(
                    ctx.p4, view_name)
                if not shared_in_progress:
                    # Copy any recent changes from Perforce to Git.
                    try:
                        LOG.debug(
                            "bare: No git-upload-pack in progress, force non-bare"
                            " before update Git from Perforce.")
                        p4gf_git.set_bare(False)
                        p4gf_copy_p2g.copy_p2g_ctx(ctx)
                        p4gf_init_repo.process_imports(ctx)

                        # Now is also an appropriate time to clear out any stale Git
                        # Swarm reviews. We're pre-pull, pre-push, time when we've
                        # got exclusive write access to the Git repo,
                        GSReviewCollection.delete_refs_for_closed_reviews(ctx)

                    except p4gf_lock.LockCanceled as lc:
                        LOG.warning(str(lc))
                    except:
                        # Dump failure to log, BEFORE cleanup, just in case
                        # cleanup ALSO fails and throws its own error (which
                        # happens if we're out of memory).
                        LOG.error(traceback.format_exc())

                        if repo_created:
                            # Return to the original working directory to allow the
                            # config code to call os.getcwd() without dying, since
                            # we are about to delete the current working directory.
                            os.chdir(cwd)
                            p4gf_server_common.cleanup_client(ctx, view_name)
                        raise

                if poll_only:
                    code = os.EX_OK
                else:

                    git_caller = functools.partial(_call_git, args, ctx)
                    try:

                        # Deep in call_git(), we grab an 'p4 reviews' lock on
                        # ctx.clientmap's LHS. Switch that clientmap to our
                        # full union view to prevent simultaneous 'git push'es
                        # from clobbering each other in some shared depot
                        # branch. Must include all lightweight branches, too.
                        ctx.switch_client_view_to_union()

                        exclusive = 'upload' not in args.command[0]
                        code = p4gf_call_git.call_git(git_caller, ctx,
                                                      view_name, view_lock,
                                                      exclusive)
                        if is_push:
                            GSReviewCollection.post_push(ctx)
                    except p4gf_atomic_lock.LockConflict as lc:
                        sys.stderr.write("{}\n".format(lc))
                        code = os.EX_SOFTWARE

            p4gf_gc.process_garbage(NTR('at end of auth_server'))
            if LOG.isEnabledFor(logging.DEBUG):
                end_time = time.time()
                frm = NTR("Runtime: preparation {} ms, lock acquisition {} ms,"
                          " processing {} ms")
                LOG.debug(
                    frm.format(before_lock_time - start_time,
                               after_lock_time - before_lock_time,
                               end_time - after_lock_time))
        return code
Exemplo n.º 9
0
def _wsgi_app(environ, start_response):
    """
    WSGI application to process the incoming Git client request. This is
    nearly equivalent to p4gf_auth_server.main() with the exception of
    input validation and error handling.
    """
    p4gf_log.record_http(environ)
    p4gf_version.log_version()
    _log_environ(environ)
    p4gf_version.version_check()
    LOG.debug("processing HTTP request, pid={}".format(os.getpid()))
    # Keep the content type to exactly 'text/plain' so there is at least
    # the remote chance that Git might show our error messages (does not
    # appear to work in practice, however).
    headers = [('Content-Type', 'text/plain')]

    encoding = sys.getfilesystemencoding()
    if encoding == 'ascii':
        # This encoding is wrong and will eventually lead to problems.
        LOG.error(
            "Using 'ascii' file encoding will ultimately result in errors, "
            "please set LANG/LC_ALL to 'utf-8' in web server configuration.")
        start_response(_('500 Internal Server Error'), headers)
        return [b"Filesystem encoding not set to acceptable value.\n"]

    # Sanity check the request.
    for (name, status, msg) in _REQUIRED_HTTP_PARAMS:
        if name not in environ:
            start_response(status, headers)
            return [msg.encode('UTF-8')]

    input_name = environ['wsgi.input']
    # Extract the view_name_git by removing the expected git request suffixes
    path_info = environ['PATH_INFO']
    git_suffixes = [
        '/info/refs', '/HEAD', '/git-upload-pack', '/git-receive-pack'
    ]
    path_end = len(path_info)
    for suffix in git_suffixes:
        try:
            path_end = path_info.index(suffix)
            break
        except ValueError:
            pass
    # slice away the leading slash and the trailing git request suffixes
    view_name_git = path_info[1:path_end]
    # and remove the view_name_git from the front of PATH_INFO
    environ['PATH_INFO'] = path_info[path_end:]
    LOG.debug("new PATH_INFO {0} view_name_git {1}".format(
        environ['PATH_INFO'], view_name_git))

    if not view_name_git:
        start_response(_('400 Bad Request'), headers)
        msg = _('Missing required repository name in URL\n')
        return [msg.encode('UTF-8')]
    # translate '/' ':' ' ' .. etc .. for internal view_name
    view_name = p4gf_translate.TranslateReponame.git_to_repo(view_name_git)
    LOG.debug("public view_name: {0}   internal view_name: {1}".format(
        view_name_git, view_name))

    audit_logger = p4gf_server_common.ExceptionAuditLogger()
    p4_closer = p4gf_create_p4.Closer()
    sink = OutputSink()
    temp_deleter = deleting(input_name)
    mirror_closer = unmirror(view_name)
    with audit_logger   \
        , p4_closer     \
        , sink          \
        , temp_deleter  \
        , mirror_closer:
        LOG.debug(p4gf_log.memory_usage())
        start_time = time.time()

        p4gf_util.reset_git_enviro()
        p4 = p4gf_create_p4.create_p4()
        if not p4:
            start_response(_('500 Internal Server Error'), headers)
            return [b"Perforce connection failed\n"]
        LOG.debug("connected to P4: %s", p4)

        p4gf_server_common.check_readiness(p4)
        p4gf_server_common.check_lock_perm(p4)
        if not p4gf_server_common.check_protects(p4):
            p4gf_server_common.raise_p4gf_perm()

        user = environ['REMOTE_USER']
        if p4gf_server_common.run_special_command(view_name, p4, user):
            start_response(_('200 OK'), headers)
            return [sink.readall()]
        command = _get_command(environ)
        if not command:
            start_response(_('400 Bad Request'), headers)
            return [b"Unrecognized service\n"]
        # Other places in the Perforce-to-Git phase will need to know the
        # name of client user, so set that here. As for Git-to-Perforce,
        # that is handled later by setting the REMOTE_USER envar. Notice
        # also that we're setting os.environ and not 'environ'.
        os.environ[p4gf_const.P4GF_AUTH_P4USER] = user
        # Likewise, some code needs a hint that the request is coming over
        # one protocol (HTTP) or the other (SSH).
        os.environ['REMOTE_ADDR'] = environ['REMOTE_ADDR']

        # Initialize the external process launcher early, before allocating lots
        # of memory, and just after all other conditions have been checked.
        p4gf_proc.init()
        # Prepare for possible spawn of GitMirror worker process by forking
        # now before allocating lots of memory.
        p4gf_gitmirror.setup_spawn(view_name)
        # Kick off garbage collection debugging, if enabled.
        p4gf_gc.init_gc()

        # Go no further, create NOTHING, if user not authorized.
        # We use the translated internal view name here for perm authorization
        required_perm = p4gf_server_common.COMMAND_TO_PERM[command]
        view_perm = p4gf_group.ViewPerm.for_user_and_view(
            p4, user, view_name, required_perm)
        try:
            p4gf_server_common.check_authorization(p4, view_perm, user,
                                                   command, view_name)
        except p4gf_server_common.CommandError as ce:
            start_response(_('403 Forbidden'), headers)
            return [str(ce).encode('UTF-8')]

        # Create Git Fusion server depot, user, config. NOPs if already created.
        p4gf_init.init(p4)

        before_lock_time = time.time()
        with p4gf_lock.view_lock(p4, view_name) as view_lock:
            after_lock_time = time.time()

            # Create Git Fusion per-repo client view mapping and config.
            init_repo_status = p4gf_init_repo.init_repo(
                p4, view_name, view_lock)
            if init_repo_status == p4gf_init_repo.INIT_REPO_OK:
                repo_created = True
            elif init_repo_status == p4gf_init_repo.INIT_REPO_EXISTS:
                repo_created = False
            elif init_repo_status == p4gf_init_repo.INIT_REPO_NOVIEW:
                start_response(_('404 Not Found'), headers)
                return [sink.readall()]
            else:
                start_response(_('500 Internal Server Error'), headers)
                return [b"Repository initialization failed\n"]

            # If authorization came from default, not explicit group
            # membership, copy that authorization to a group now. Could
            # not do this until after p4gf_init_repo() has a chance to
            # create not-yet-existing groups.
            if view_perm:
                view_perm.write_if(p4)

            # Now that we have valid git-fusion-user and
            # git-fusion-<view> client, replace our temporary P4
            # connection with a more permanent Context, shared for the
            # remainder of this process.
            with p4gf_context.create_context(view_name, view_lock) as ctx:
                LOG.debug("reconnected to P4, p4gf=%s", ctx.p4gf)
                ctx.log_context()

                # cd into the work directory. Not all git functions react well
                # to --work-tree=xxxx.
                cwd = os.getcwd()
                os.chdir(ctx.view_dirs.GIT_WORK_TREE)

                # Only copy from Perforce to Git if no other process is cloning
                # from this Git repo right now.
                shared_in_progress = p4gf_lock.shared_host_view_lock_exists(
                    ctx.p4, view_name)
                if not shared_in_progress:
                    # Copy any recent changes from Perforce to Git.
                    try:
                        LOG.debug(
                            "bare: No git-upload-pack in progress, force non-bare"
                            " before update Git from Perforce.")
                        p4gf_git.set_bare(False)
                        p4gf_copy_p2g.copy_p2g_ctx(ctx)
                        p4gf_init_repo.process_imports(ctx)

                        # Now is also an appropriate time to clear out any stale Git
                        # Swarm reviews. We're pre-pull, pre-push, time when we've
                        # got exclusive write access to the Git repo,
                        GSReviewCollection.delete_refs_for_closed_reviews(ctx)

                    except p4gf_lock.LockCanceled as lc:
                        LOG.warning(str(lc))
                    except:
                        # Dump failure to log, BEFORE cleanup, just in case
                        # cleanup ALSO fails and throws its own error (which
                        # happens if we're out of memory).
                        LOG.error(traceback.format_exc())

                        if repo_created:
                            # Return to the original working directory to allow the
                            # config code to call os.getcwd() without dying, since
                            # we are about to delete the current working directory.
                            os.chdir(cwd)
                            p4gf_server_common.cleanup_client(ctx, view_name)
                        raise

                try:
                    exclusive = 'upload' not in command
                    is_push = 'upload' not in command
                    git_caller = functools.partial(_call_git, input_name,
                                                   environ, ctx)
                    p4gf_call_git.call_git(git_caller, ctx, view_name,
                                           view_lock, exclusive)
                    if is_push:
                        GSReviewCollection.post_push(ctx)
                except p4gf_atomic_lock.LockConflict as lc:
                    start_response(_('500 Internal Server Error'), headers)
                    return ["{}".format(lc).encode('UTF-8')]

        p4gf_gc.process_garbage('at end of auth_server')
        if LOG.isEnabledFor(logging.DEBUG):
            end_time = time.time()
            frm = NTR(
                'Runtime: preparation {} ms, lock acquisition {} ms, processing {} ms'
            )
            LOG.debug(
                frm.format(before_lock_time - start_time,
                           after_lock_time - before_lock_time,
                           end_time - after_lock_time))
        return []