require 'hws_settings' require 'projects/project_service' require 'base64' require 'hws_strings' module HelixVersioningEngine # Define simple projects based on a single directory location in a Helix # Versioning Engine. # # This project service uses the HVE_PROJECTS_PATH setting to list available # projects on the particular server. If you only have one Helix Versioning # Engine, it's suitable to create this as a systemwide default. Otherwise, # you likely only want to specify this via headers. # # This will return everything as "helix versioning engine" project. class HVEProjectsService HVE_CONTENT_TYPE = 'application/vnd.perforce.project.hve.v1+json' HVE_ID = 'hveProject' # Rack environment. attr_accessor :env def initialize(env: nil) @env = env end # List HVE Projects as configured in the system. # # See the Appendix in the documentation for details on values. def list(details: false, extension: nil) return if extension and (extension != HVE_ID or extension != HVE_CONTENT_TYPE) project_dirs = list_project_names project_names = project_dirs.map { |d| File.basename(d) } if details project_names.map { |n| fetch_by_name(n) } else project_names.map { |n| encode_name(n) } end end def list_project_names pattern = "#{hve_projects_path}/*" results = p4.run_dirs(pattern) results.map { |r| r['dir'] } end # The ID is a URL encoded version of the directory name under # HVE_PROJECTS_PATH. # # This will unencode the ID and fetch by name. def fetch(id) name = unencode_name(id) fetch_by_name(name) end # Returns the project's "details" based on the project name. # # No validation is done to ensure this directory actually exists in the # system. def fetch_by_name(name) id = encode_name(name) { 'id': id, 'name': name, 'server': server_uri_for_id(id), HVE_ID => { 'depotPath': depot_path_for_name(name) } } end # Generate a new client that only contains the project mapping. # # The client name is a combination of user, project, and device. We prefix # it with "_hve" just for clarity. # # We do not host lock the client. # # @param project_id {String} Our encoded project name # @param device {String} A device ID, like a hostname # @param root {String} The `Root` value for the client parameter def create_client(project_id, device, root) client_name = "_hve_#{user}_#{project_id}_#{device}" client_spec = p4.fetch_client(client_name) client_spec._root = root; client_spec._host = nil; client_spec._options = 'allwrite noclobber nocompress unlocked nomodtime rmdir'; project_name = unencode_name(project_id) client_spec._view = [ %Q|"#{depot_path_for_name(project_name)}/..." "//#{client_name}/..."| ] results = p4.save_client(client_spec) puts "new client results: #{results}" client_name end # Find the latest submitted change for the project # # Use the 'p4 changes -m 1 -s submitted [depot path]' # # @param project_id [String] The encoded project ID def find_latest_change_for_project(project_id) project_name = unencode_name(project_id) depot_path = depot_path_for_name(project_name) results = p4.run_changes('-m', '1', '-s', 'submitted', "#{depot_path}/...") results.first['change'] unless results.empty? end # The HVE project 'changelist' is a pending changelist whose description is # `_hve_[user]_[project_id]` # # We use 'changes -l' to find the change to match potentially long project # names. def find_pending_change_for_project(project_id) results = p4.run_changes('-l', '-u', user, '-s', 'pending') change = results.find { |r| r['desc'].include?(project_id) } change['change'] if change end # If the user doesn't have a current pending change for the project, # create one, and return that. def create_pending_change(project_id) change = find_pending_change_for_project(project_id) return change if change change_spec = p4.fetch_change change_spec._description = "_hws_#{user}_#{project_id}" save_results = p4.save_change(change_spec) change = save_results.first.gsub(/Change (\d+) created./, '\1') # If we don't reset the client of this pending change, we won't be able # to cleanup the temporary client. change_spec = p4.fetch_change(change) change_spec._client = 'INVALID' p4.save_change(change_spec) change end def encode_name(name) HWSStrings.component_encode(name) end def unencode_name(name) HWSStrings.component_decode(name) end def server_uri_for_id(id) "p4://#{userinfo}#{server}#{safe_hve_projects_path}/#{id}" end def depot_path_for_name(name) "#{hve_projects_path}/#{name}" end def p4 env['p4'] || fail('p4 required to use HVEProjects') end def hve_projects_path env['hws_settings'].HVE_PROJECTS_PATH || fail('HVE_PROJECTS_PATH not set') end def safe_hve_projects_path hve_projects_path.gsub('//', '/') end def user env['AUTH_CREDENTIALS'].first end # For HVE Projects, it may be interesting to people to see various # connection settings for each server URL. def userinfo data = {} if env['hws_settings'].P4CHARSET data['P4CHARSET'] = env['hws_settings'].P4CHARSET end if data.keys.empty? '' else encoded_data = data.map {|k,v| "#{k}=#{v}"}.join(';') "#{encoded_data}@" end end def server return p4port if p4port.include?(':') host = p4host ? p4host : 'localhost' port = p4port "#{host}:#{port}" end def p4port env['hws_settings'].P4PORT || fail('P4PORT setting not available') end def p4host env['hws_settings'].P4HOST end end end