#!/usr/bin/env ruby # NOTE: This was a prototype script created by Alan Teague for validating the # Helix Sync have table algorithm. require 'P4' require 'pp' require 'json' require 'digest' require 'set' $have_table_file = '.have_table' $device_client = '' $shelf_client = ENV['SHELF_CLIENT'] $shelf_change = '' $shelf_gen = '' $shelf_working = {} $shelf_contents = {} $shelf_submitted = false $have_table = {} $last_change = '' $depot_sync = {} $client_sync = {} $depot_rec = {} def spew( msg, obj ) if obj != nil and !obj.empty? then puts(msg) pp(obj) end end def local_path( p4, f ) where = p4.run_where([f]) if where != nil then return where[0]['path'] else return nil end end def grab_shelf( p4 ) # take over shelf ownership if $shelf_change != '' then change = p4.fetch_change($shelf_change) change["Client"] = $device_client p4.save_change( change ) end end def release_shelf( p4 ) # take over shelf ownership if $shelf_change != '' then change = p4.fetch_change($shelf_change) change["Client"] = $shelf_client p4.save_change( change ) end end def create_shelf( p4 ) change = p4.fetch_change change['Description'] = '{"shelfGen":"0", "working":{}}' change["Client"] = $shelf_client msg = p4.save_change( change ) changes = p4.run_changes( ['-m1', "-c", "#{$shelf_client}"] ) $shelf_change = changes[0]['change'] end def load_have_table() if File.exist?($have_table_file) then File.open($have_table_file, "r") do |f| $have_table = JSON.load(f) $shelf_change = $have_table['shelfNum'] end end end def get_last_change( p4 ) changes = p4.run_changes(['-m1', "//#{$device_client}/..."]) if !changes.empty? then $last_change = changes[0]['change'] else $last_change = '0' end end def load_shelf( p4 ) if $shelf_change == '' then changes = p4.run_changes( ["-m1", "-s", "shelved","-c", "#{$shelf_client}"] ) if !changes.empty? then $shelf_change = changes[0]['change'] else return end end shelf = p4.run_describe( ['-S', '-s', "#{$shelf_change}"] ) if shelf[0] == nil then # p4 change -o -O 2 -s puts("Searching for shelf") change = p4.run_change( ["-o", "-s", "-O", "#{$shelf_change}"] ) pp(change) if change != nil and !change.empty? then $shelf_change = change[0]['Change'] puts("...found #{$shelf_change}") shelf = p4.run_describe( ['-S', '-s', "#{$shelf_change}"] ) else puts("Bad news, cannot resolve previous shelf") end end shelf = shelf[0] # Extract details from shelf desc if shelf['status'] == "submitted" then $shelf_submitted = true end shelf_desc = shelf['desc'] s = JSON.parse(shelf_desc) $shelf_gen = s['shelfGen'] $shelf_working = s['working'] ['depotFile', 'action', 'type', 'rev', 'fileSize', 'digest', 'fromFile', 'fromRev'].each { |k| if !shelf.has_key?(k) then shelf[k] = Array.new end } shelf_raw = shelf['depotFile'].zip( shelf['action'], shelf['type'], shelf['rev'], shelf['fileSize'], shelf['digest'], shelf['fromFile'], shelf['fromRev']) $shelf_contents = Hash.new shelf_raw.each { |e| $shelf_contents[e[0]] = e[1..-1] } end def load_sync_files( p4 ) files = p4.run_sync(['-n', "//#{$device_client}/...@#{$last_change}"]) $depot_sync = Hash.new $client_sync = Hash.new files.each { |f| $depot_sync[f['depotFile']] = f $client_sync[f['clientFile']] = f } end def load_rec_files( p4 ) files = p4.run_reconcile(['-n', "//#{$device_client}/..."]) files.delete_if { |entry| entry.is_a? String } $depot_rec = Hash.new $depot_to_client_map = Hash.new files.each { |f| $depot_rec[f['depotFile']] = f if f['action'] == 'move/add' then f['action'] = 'add' elsif f['action'] == 'move/delete' then f['action'] = 'delete' end $depot_to_client_map[f['depotFile']] = f['clientFile'] } end def save_have_table() have_table = Hash.new have_table['shelfGen'] = $shelf_gen have_table['shelfNum'] = $shelf_change have_table['lastChange'] = $last_change have_table['have'] = $shelf_working File.open($have_table_file, "w") do |f| f.write(JSON.pretty_generate(have_table)) end end def update_disk( p4, updates ) if updates.empty? then return end grab_shelf( p4 ) args = ["-s", "#{$shelf_change}"] args.concat(updates.to_a) stuff = p4.run_unshelve(args) args = ["-k"] args.concat(updates.to_a) stuff = p4.run_revert(args) release_shelf( p4 ) save_have_table end def update_shelf( p4, update_to_shelf, reverting ) if update_to_shelf.empty? and reverting.empty? return end if $shelf_change == '' then create_shelf( p4 ) end grab_shelf( p4 ) updated_working = false updated_gen_number = false update_to_shelf.each { |f| stuff = p4.run([$depot_rec[f]['action'], "-c", $shelf_change, f]) } if !update_to_shelf.empty? then stuff = p4.run_shelve(["-c", "#{$shelf_change}", "-f"]) stuff = p4.run_revert(["-k", "//#{$device_client}/..."]) load_shelf(p4) if !updated_gen_number then $shelf_gen = Integer($shelf_gen) + 1 updated_gen_number = true end update_to_shelf.each { |f| fileSize = $shelf_contents[f][3] fileDigest = $shelf_contents[f][4] $shelf_working[f] = ["#{$shelf_gen}", "#{fileSize}", "#{fileDigest}", nil, Time.now.to_i] } updated_working = true end if !reverting.empty? then reverting.each { |f| if !$shelf_working[f][3] then if !updated_gen_number then $shelf_gen = Integer($shelf_gen) + 1 updated_gen_number = true end $shelf_working[f][0] = $shelf_gen $shelf_working[f][3] = true p4.run_shelve(["-d", "-c", "#{$shelf_change}", f]) end } updated_working = true end if updated_working then new_desc = Hash.new new_desc["shelfGen"] = $shelf_gen new_desc["working"] = $shelf_working change = p4.fetch_change($shelf_change) change["Description"] = JSON.generate(new_desc) p4.save_change( change ) save_have_table end release_shelf( p4 ) end p4 = P4.new $device_client = ENV['P4CLIENT'] puts("pwd: #{Dir.pwd}") puts("\nDeviceSync:\nDevice: #{$device_client}\nShelf: #{$shelf_client}") begin p4.connect p4.exception_level = P4::RAISE_ERRORS info = p4.run_info puts("info: #{info}") # Get HAVE, WORKING(shelf), CONTENTS(shelf), SYNC-N, REC-N, and LAST_CHANGE # Run rec -n, then last_change, then sync -n # rec is not bound by change number so run it first # last_change is needed by sync -n load_rec_files( p4 ) get_last_change( p4 ) load_sync_files( p4 ) # These two can run in either order and before or after the rec/sync loading load_have_table load_shelf( p4 ) if $shelf_submitted and ($last_change < $shelf_change) then puts("Start over again, submit happened midstream") exit end # Review disk for changes from server or from shelf changed_on_disk = Set.new not_changed_on_disk = Set.new have = $have_table['have'] if have == nil then have = Hash.new end $depot_rec.each { |f,e| if have.key?(f) then if have[f][1] == '' then if File.exist?(e['clientFile']) then puts("#{f}: exists now") changed_on_disk.add(f) else not_changed_on_disk.add(f) end elsif !File.exist?(e['clientFile']) then puts("#{f}: not exists") changed_on_disk.add(f) elsif Integer(have[f][1]) != File.size(e['clientFile']) or have[f][2] != String(Digest::MD5.file(e['clientFile'])).upcase! then if Integer(have[f][1]) != File.size(e['clientFile']) then puts("#{f}: filesize") pp(e['clientFile']) pp(File.size(e['clientFile'])) else puts("#{f}: digest") pp(e['clientFile']) pp(String(Digest::MD5.file(e['clientFile']))) end changed_on_disk.add( f ) else not_changed_on_disk.add( f ) end else changed_on_disk.add( f ) end } spew("\nChanged on disk:", changed_on_disk) spew("\nNot changed on disk:", not_changed_on_disk) changed_on_shelf = Set.new not_changed_on_shelf = Set.new $shelf_working.each { |f,e| if have.key?(f) then if e == have[f] then not_changed_on_shelf.add( f ) else changed_on_shelf.add( f ) end else changed_on_shelf.add( f ) end } spew("\nChanged on shelf:", changed_on_shelf) spew("\nNot changed on shelf:", not_changed_on_shelf) update_to_shelf = Set.new shelf_conflict = Set.new changed_on_disk.each { |f| if not_changed_on_shelf.include?(f) then update_to_shelf.add(f) elsif changed_on_shelf.include?(f) then shelf_conflict.add(f) else update_to_shelf.add(f) end } spew("\nUpdate shelf with:", update_to_shelf) spew("\nShelf conflicts:", shelf_conflict) update_from_shelf = Set.new reverted_shelf = Set.new changed_on_shelf.each { |f| if not_changed_on_disk.include?(f) then if $shelf_working[f][3] then reverted_shelf.add(f) else update_from_shelf.add(f) end elsif !changed_on_disk.include?(f) then update_from_shelf.add(f) # else already in shelf_conflict end } spew("\nUpdate from shelf:", update_from_shelf) reverted_disk = Set.new reverted_conflicts = Set.new have.each { |f,e| if !$depot_rec.key?(f) then if not_changed_on_shelf.include?(f) then if !e[3] then reverted_disk.add(f) end elsif changed_on_shelf.include?(f) then reverted_conflicts.add(f) else # should never happen: HAVE record but no WORKING record reverted_disk.add(f) end end } spew("\nReverted shelf files:", reverted_shelf) spew("\nReverted disk files:", reverted_disk) spew("\nReverted conflicts:", reverted_conflicts) shelf_conflict.each { |f| puts("Conflict for #{f}") local_file = local_path(p4, f) if File.exist?(local_file) then conflict_name = "#{f}.conflict" File.rename(local_file, local_path(p4, conflict_name)) update_to_shelf.add(conflict_name) $depot_rec[conflict_name] = Hash.new $depot_rec[conflict_name]['action'] = 'add' end update_from_shelf.add( f ) } reverted_conflicts.each { |f| puts("Revert conflict for #{f}") update_from_shelf.add( f ) } # if shelf was submitted if $shelf_submitted then puts("DRAFT - Handle resolving submitted shelf") # update_to_shelf CHANGED ON DISK @ MY KNOWN SHELF # reverted_disk NEED NEW VERSION, CLEAR HAVE # update_from_shelf NEED NEW VERSION, CLEAR HAVE # reverted_shelf NEED NEW VERSION, CLEAR HAVE # $depot_sync NEED THESE MINUS update_to_shelf spew("\nNeed to shelve:", update_to_shelf) File.delete($have_table_file) $depot_sync.delete_if{|k,v| update_to_shelf.include?(k)} # Need to rerun because there may already be a shelf # that needs to be resolved against puts("\nRerun deviceSync after this run completes") else update_shelf( p4, update_to_shelf, reverted_disk ) update_disk( p4, update_from_shelf ) $depot_sync.delete_if{|k,v| $shelf_working.include?(k) and !$shelf_working[k][3]} if reverted_shelf.empty? then puts("\nNo files to force sync") else puts("\nForce sync files:(if not in sync)") reverted_shelf.each { |f| if !$depot_sync.include?(f) then puts(f) lf = local_path(p4, f) if lf != nil and File.exist?(lf) then File.delete(lf) end stuff = p4.run_sync(["-f", "#{f}@#{$last_change}"]) end } save_have_table end end if $depot_sync.empty? then puts("\nNo files to sync") else spew("\nSync files:", $depot_sync) $depot_sync.each { |f,v| p4.run_sync(["#{v['depotFile']}@#{$last_change}"]) } end rescue P4Exception p4.errors.each { |e| pp( e ) } ensure p4.disconnect end