require 'base64' require_relative 'util' module HelixVersioningEngine # This class assists in creating changelists based on an array of file # changes, some of which may be file uploads. # # This is the implementation for file uploads and change creation. class ChangeService # The P4 connection we're using, that should already be logged in for a # particular user. attr_reader :p4 # The name of the temporary client attr_reader :client_name # The root directory of the temporary client attr_reader :client_root def initialize(p4: nil, client_name: nil, client_root: nil) @p4 = p4 @client_name = client_name @client_root = client_root end def submit(files: [], description: 'Updating files') change_id = Util.init_changelist(p4, description) begin files = files.map { |f| f.is_a?(ChangeService::File) ? f : ChangeService::File.new(f) } collect_file_results(files) files.each { |f| f.call(p4, change_id, client_root) } p4.run_submit('-c', change_id) rescue StandardError => ex # Delete the changelist p4.at_exception_level(P4::RAISE_NONE) do p4.run_change('-d', '-f', change_id) if Util.error?(p4) puts "possible issues deleting change #{change_id}: #{p4.messages}" end end raise ex end end # Handles tracking 'file modification' operations on a single file for a # change. # # One important aspect of initializing these files is assigning a 'files' # result. The ChangeHelper pretty much grabs the output of 'p4 files' for # all file changes, which lets us know if a file exists or not in the # system, which changes downstream behavior during an upload process. class File attr_accessor :depot_file attr_accessor :action attr_accessor :content # For *some* actions, like a move, we need this indicated by the user. attr_accessor :from_depot_file # The output of p4 files on this depot_file path, if it exists. attr_accessor :file_result # If set, this should be the version number we require before running. # If you want to require a new file, this should be set to 0. Otherwise # specify the version directly. attr_accessor :require_version # Notice how the file *must* be specified using our 'external normalized' # data style. def initialize(depot_file: nil, action: nil, content: nil, from_depot_file: nil, require_version: nil) @depot_file = depot_file @action = action @content = content @from_depot_file = from_depot_file @require_version = require_version end # The "external" JSON representation uses a CamelCase style string. def self.from_json(obj) args = {} args[:depot_file] = obj['DepotFile'] if obj.key?('DepotFile') args[:action] = obj['Action'] if obj.key?('Action') args[:from_depot_file] = obj['FromDepotFile'] if obj.key?('FromDepotFile') args[:require_version] = obj['RequireVersion'] if obj.key?('RequireVersion') content_base64 = obj['Content'] if obj.key?('Content') args[:content] = Base64.decode64(content_base64) if content_base64 self.new(args) end def exists? !file_result.nil? && file_result['action'] != 'delete' end # # This really should only be called by ChangeHelper::call to do work # once we have associated any file_result and from_file properties. def call(p4, change_id, client_root) case action when 'upload' upload_file(p4, change_id, client_root) when 'branch' integrate_file(p4, change_id) when 'move' move_file(p4, change_id) end end def upload_file(p4, change_id, client_root) Util.assert_no_special_paths(@depot_file.split('/')) if !require_version.nil? if require_version == 0 && exists? msg = "assertion failed: file #{@depot_file} exists" raise P4Error.default_error(msg) end cur_version = 0 cur_version = file_result['rev'].to_i if exists? if cur_version != require_version.to_i msg = "assertion failed: file #{@depot_file} not at required " \ "version #{require_version}, " \ "current version is #{cur_version}" raise P4Error.default_error(msg) end end if exists? p4.run_sync(depot_file) Util.mark_change('edit', p4, change_id, client_root, depot_file) Util.save_content(client_root, depot_file, content) else Util.save_content(client_root, depot_file, content) Util.mark_change('add', p4, change_id, client_root, depot_file) end end def integrate_file(p4, change_id) p4.run_integrate('-c', change_id, from_depot_file, depot_file) end def move_file(p4, change_id) p4.run_move('-c', change_id, from_depot_file, depot_file) end end private # Runs p4 files on all our depot paths, then updates each file model # that exists. def collect_file_results(files) file_results = p4.run_files(depot_files(files)) file_results.each do |file_result| file = file_for_depot_file(files, file_result['depotFile']) file.file_result = file_result if file end end def depot_files(files) files.map(&:depot_file) end def file_for_depot_file(files, depot_file) files.find { |f| f.depot_file == depot_file } end end end
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#1 | 15741 | ptomiak | Branch HWS for my use. | ||
//guest/perforce_software/helix-web-services/main/source/helix_web_services/lib/helix_versioning_engine/change_service.rb | |||||
#1 | 15622 | tjuricek |
Move source code to 'source/' subdirectory of branch. build/ will remain where it is. |
||
//guest/perforce_software/helix-web-services/main/helix_web_services/lib/helix_versioning_engine/change_service.rb | |||||
#3 | 15189 | tjuricek | Update files implementation, testing, and documentation. | ||
#2 | 15110 | tjuricek | Revise changes methods for new p4 connection handling, add server specs, remove model references in client, and update asciidoc documentation. | ||
#1 | 15032 | tjuricek |
Starting config and doc revisions. System is now broken while revisions underway. Configuration of the p4d connection is now done via a single HWSSettings middleware object injected into the Rack env. The HWSP4Cleanup middleware now cleans up any p4 injected into the Rack env. The Auth::App class now mostly just contains one method to generate a p4 ticket. /auth/v1/login. Added yard documentation for the main project. Yard docs have been reconfigured to dump into build/ directories. This should probably be done with each release. Hm... The top level rake file contains a task, 'all:doc', to update our documentation. This should probably be run for each checkin. Hm... Specs are now using Rack::Test on top of a 'live' p4d. I'd suggest you still use the p4util mechanism, which now dumps to a /tmp folder, so we can safely add P4IGNORE rules back into your local .p4config file. Old 'perforce' application now called 'helix_versioning_engine'. Removing cache data. Helix Sync may be slow. It may also get axed. We'll see. |
||
//guest/perforce_software/helix-web-services/main/helix_web_services/lib/perforce/change_service.rb | |||||
#3 | 13891 | tjuricek |
Added a 'member' concept to projects. members is the start of a basic project 'role' definition. In Helix sync, this is mostly just used to indicate this is a "my Project" in the UI and a project member can have the ability to leave the project or add someone else. Eventually, we expect "members" to mean more, like, is part of a group that has write access to the files of the project. But that sort of definition is very TBD. |
||
#2 | 13846 | tjuricek | Setup development rules for creating/initializing PostgreSQL, and updated some of the ProjectService specs. | ||
#1 | 13839 | tjuricek |
Conversion of the p4_project_service microservice to new monolithic system. This may not have an HTTP front end in the monolithic system. Project services are really just about how the core object model is structured. It's likely that each application will add their own wrinkles and extensions to the system, so it's unlikely we'll need a generic "project model". Exactly how extensions are registered and used is still a bit TBD at the moment. Previously they were to be registered webhooks, that model may change. Does not include tests yet. |
||
//guest/perforce_software/helix-web-services/main/helix_web_services/lib/perforce/change_helper.rb | |||||
#2 | 13808 | tjuricek | Finish converting most of the core p4d API into the new monolithic system. | ||
#1 | 13799 | tjuricek |
Start with branch specs hosting in a new monolithic 'helix web services' project. Converting from a microservice to a monolithic architecture due to resource constraints at getting a deployable system running. Additionally, since it's not expected that people will upgrade often, the major benefit of microservices - being able to add services individually without affecting others - is not really a major benefit. The Ruby SDK will be consolidated into a single 'helix web services client' project. It may end up being distributed via Rubygems. This only runs branch specs at the moment. I want to get a CD pipeline setup for the monolithic server before revising more methods. |
||
//guest/perforce_software/helix-web-services/main/p4_web_api/p4_web_api/lib/p4_web_api/change_helper.rb | |||||
#1 | 13412 | tjuricek |
Initial version of the web-services mainline. This is a collection of several projects, that will likely often get released together, though many of them may not always be relevant. See the README for more information. |