@@ -634,6 +634,57 @@ proc _progress {args} {
634634 ui_progress_generic {*}${args}
635635}
636636
637+ proc _get_port_conflicts {port} {
638+ global registry::tdbc_connection
639+ if {![info exists tdbc_connection]} {
640+ registry ::tdbc_connect
641+ }
642+ variable conflicts_stmt
643+ if {![info exists conflicts_stmt]} {
644+ set query {SELECT files.id, files.path, files.actual_path FROM
645+ (SELECT path FROM files where id = :thisport_id)
646+ AS thisport_files
647+ INNER JOIN files ON thisport_files.path = files.actual_path
648+ WHERE files.active = 1}
649+ set conflicts_stmt [$tdbc_connection prepare $query ]
650+ }
651+
652+ set thisport_id [$port id]
653+ $tdbc_connection transaction {
654+ set results [$conflicts_stmt execute]
655+ }
656+ set id_to_port [dict create]
657+ set path_to_port [dict create]
658+ set port_to_paths [dict create]
659+ set is_replaced [dict create]
660+ set todeactivate [dict create]
661+ set thisport_name [$port name]
662+ foreach result [$results allrows] {
663+ set id [dict get $result id]
664+ if {![dict exists $id_to_port $id ]} {
665+ dict set id_to_port $id [lindex [registry ::entry search id $id ] 0]
666+ }
667+ set conflicting_port [dict get $id_to_port $id ]
668+ if {![dict exists $is_replaced $conflicting_port ]} {
669+ lassign [mportlookup [$conflicting_port name]] _ portinfo
670+ if {[dict exists $portinfo replaced_by] && [lsearch -exact -nocase [dict get $portinfo replaced_by] $thisport_name ] != -1} {
671+ dict set is_replaced $conflicting_port 1
672+ dict set todeactivate $conflicting_port 1
673+ } else {
674+ dict set is_replaced $conflicting_port 0
675+ }
676+ }
677+ set actual_path [dict get $result actual_path]
678+ dict set path_to_port $actual_path $conflicting_port
679+ if {![dict get $is_replaced $conflicting_port ]} {
680+ set imagepath [dict get $result path]
681+ dict lappend port_to_paths $conflicting_port [list $imagepath $actual_path ]
682+ }
683+ }
684+ $results close
685+ return [list $path_to_port $port_to_paths $todeactivate ]
686+ }
687+
637688# # Activates the contents of a port
638689proc _activate_contents {port {rename_list {}}} {
639690 variable force
@@ -679,7 +730,7 @@ proc _activate_contents {port {rename_list {}}} {
679730 # Last, if the file exists, and belongs to another port, and force is set
680731 # we remove the file from the file_map, take ownership of it, and
681732 # clobber it
682- set todeactivate [list ]
733+ set todeactivate [dict create ]
683734 try {
684735 registry ::write {
685736 foreach file $imagefiles {
@@ -694,51 +745,73 @@ proc _activate_contents {port {rename_list {}}} {
694745 throw registry::image-error " Image error: Source file $srcfile does not appear to exist. Unable to activate port ${portname} ."
695746 }
696747
697- set owner [registry ::entry owner $file ]
698-
699- if {$owner ne {} && $owner ne $port } {
700- # deactivate conflicting port if it is replaced_by this one
701- set result [mportlookup [$owner name]]
702- set portinfo [lindex $result 1]
703- if {[dict exists $portinfo replaced_by] && [lsearch -exact -nocase [dict get $portinfo replaced_by] $portname ] != -1} {
704- # we'll deactivate the owner later, but before activating our files
705- lappend todeactivate $owner
706- set owner " replaced"
707- }
708- }
709-
710- if {$owner ne " replaced" } {
711- if {$force } {
712- # if we're forcing the activation, then we move any existing
713- # files to a backup file, both in the filesystem and in the
714- # registry
715- if { ![catch {::file type $file }] } {
716- set bakfile " ${file}${baksuffix} "
717- _progress intermission
718- ui_warn " File $file already exists. Moving to: $bakfile ."
719- ::file rename -force -- $file $bakfile
720- lappend backups $file
721- }
722- if { $owner ne {} } {
723- $owner deactivate [list $file ]
724- $owner activate [list $file ] [list " ${file}${baksuffix} " ]
748+ if {![catch {::file type $file }]} {
749+ if {![info exists conflicts_path_to_port]} {
750+ # Check for conflicting ports. 'todeactivate' contains ports replaced by this one,
751+ # which we'll deactivate later, but before activating our files.
752+ lassign [_get_port_conflicts $port ] conflicts_path_to_port conflicts_port_to_paths todeactivate
753+ if {!$force && [dict size $conflicts_port_to_paths ] > 0} {
754+ set msg " The following ports have active files that conflict with ${portname} 's:\n "
755+ foreach conflicting_port [dict keys $conflicts_port_to_paths ] {
756+ append msg " [ $conflicting_port name] @[ $conflicting_port version] _[ $conflicting_port revision] [ $conflicting_port variants] \n "
757+ set conflicting_paths [dict get $conflicts_port_to_paths $conflicting_port ]
758+ set pathcounter 0
759+ set pathtotal [llength $conflicting_paths ]
760+ foreach {_ actual_path} $conflicting_paths {
761+ if {$pathcounter >= 3 && $pathtotal > 4} {
762+ append msg " (... [ expr {$pathtotal - $pathcounter }] more not shown)\n "
763+ break
764+ }
765+ append msg " ${actual_path} \n "
766+ incr pathcounter
767+ }
768+ }
769+ append msg " Image error: Conflicting file(s) present. Please deactivate the conflicting port(s) first, or use 'port -f activate $portname ' to force the activation."
770+ throw registry::image-error $msg
725771 }
772+ }
773+ if {[dict exists $conflicts_path_to_port $file ]} {
774+ set owner [dict get $conflicts_path_to_port $file ]
726775 } else {
727- # if we're not forcing the activation, then we bail out if
728- # we find any files that already exist, or have entries in
729- # the registry
730- if { $owner ne {} && $owner ne $port } {
731- set msg " Image error: $file is being used by the active [ $owner name] port. Please deactivate this port first, or use 'port -f activate $portname ' to force the activation."
732- # registry::entry close $owner
733- throw registry::image-error $msg
734- } elseif { $owner eq {} && ![catch {::file type $file }] } {
776+ set owner {}
777+ }
778+ if {$owner eq {} || ![dict exists $todeactivate $owner ]} {
779+ if {$force } {
780+ # if we're forcing the activation, then we move any existing
781+ # files to a backup file, both in the filesystem and in the
782+ # registry
783+ if {$owner ne {}} {
784+ # Rename all conflicting files for this owner.
785+ set owner_paths [list ]
786+ set owner_actual_paths [list ]
787+ foreach {path actual_path} [dict get $conflicts_port_to_paths $owner ] {
788+ lappend owner_deactivate_paths $path
789+ if {![catch {::file type $actual_path }]} {
790+ lappend owner_activate_paths $path
791+ set bakfile ${actual_path}${baksuffix}
792+ lappend owner_backup_paths $bakfile
793+ ui_warn " File $actual_path already exists. Moving to: $bakfile ."
794+ ::file rename -force -- $actual_path $bakfile
795+ lappend backups $actual_path
796+ }
797+ }
798+ $owner deactivate $owner_deactivate_paths
799+ $owner activate $owner_activate_paths $owner_backup_paths
800+ } else {
801+ # Just rename this file.
802+ set bakfile ${file}${baksuffix}
803+ _progress intermission
804+ ui_warn " File $file already exists. Moving to: $bakfile ."
805+ ::file rename -force -- $file $bakfile
806+ lappend backups $file
807+ }
808+ } else {
809+ # if we're not forcing the activation, then we bail out if
810+ # we find any files that already exist
735811 set msg " Image error: $file already exists and does not belong to a registered port. Unable to activate port ${portname} . Use 'port -f activate $portname ' to force the activation."
736812 throw registry::image-error $msg
737813 }
738814 }
739- # if {$owner ne {}} {
740- # registry::entry close $owner
741- # }
742815 }
743816
744817 # Split out the filename's subpaths and add them to the
@@ -775,7 +848,7 @@ proc _activate_contents {port {rename_list {}}} {
775848
776849 # deactivate ports replaced_by this one
777850 set deactivate_options [dict create ports_nodepcheck 1]
778- foreach owner $todeactivate {
851+ foreach owner [dict keys $todeactivate ] {
779852 _progress intermission
780853 if {$noexec || ![registry ::run_target $owner deactivate $deactivate_options ]} {
781854 deactivate [$owner name] " " " " 0 $deactivate_options
@@ -860,7 +933,7 @@ proc _activate_contents {port {rename_list {}}} {
860933 ::file rename -force -- " ${file}${baksuffix} " $file
861934 }
862935 # reactivate deactivated ports
863- foreach entry $todeactivate {
936+ foreach entry [dict keys $todeactivate ] {
864937 if {[$entry state] eq " imaged" && ($noexec || ![registry ::run_target $entry activate " " ])} {
865938 activate [$entry name] [$entry version] [$entry revision] [$entry variants] [dict create ports_activate_no-exec $noexec ]
866939 }
@@ -873,7 +946,7 @@ proc _activate_contents {port {rename_list {}}} {
873946
874947 throw [dict get $eOptions -errorcode] [dict get $eOptions -errorinfo]
875948 } finally {
876- # foreach entry $todeactivate {
949+ # foreach entry [dict keys $todeactivate] {
877950 # registry::entry close $entry
878951 # }
879952 # Only delete extracted dir if there is an archive to re-extract from
0 commit comments