2020-01-04 01:14:02 +00:00
#!/usr/bin/env python3
import sys
import os
import string
import argparse
import subprocess
import fnmatch
import pygit2
import yaml
def string_to_valid_file_name ( to_convert ) :
valid_chars = ' -_.() ' + string . ascii_letters + string . digits
result = ' '
for c in to_convert :
if c in valid_chars :
result + = c
else :
result + = ' _ '
# strip final dot, if any
if result . endswith ( ' . ' ) :
return result [ : - 1 ]
return result
class wine_sync :
def __init__ ( self , module ) :
if os . path . isfile ( ' winesync.cfg ' ) :
with open ( ' winesync.cfg ' , ' r ' ) as file_input :
2020-09-21 13:27:21 +00:00
config = yaml . safe_load ( file_input )
2020-01-04 01:14:02 +00:00
self . reactos_src = config [ ' repos ' ] [ ' reactos ' ]
self . wine_src = config [ ' repos ' ] [ ' wine ' ]
self . wine_staging_src = config [ ' repos ' ] [ ' wine-staging ' ]
else :
config = { }
self . reactos_src = input ( ' Please enter the path to the reactos git tree: ' )
self . wine_src = input ( ' Please enter the path to the wine git tree: ' )
self . wine_staging_src = input ( ' Please enter the path to the wine-staging git tree: ' )
config [ ' repos ' ] = { ' reactos ' : self . reactos_src ,
' wine ' : self . wine_src ,
' wine-staging ' : self . wine_staging_src }
with open ( ' winesync.cfg ' , ' w ' ) as file_output :
yaml . dump ( config , file_output )
self . wine_repo = pygit2 . Repository ( self . wine_src )
self . wine_staging_repo = pygit2 . Repository ( self . wine_staging_src )
self . reactos_repo = pygit2 . Repository ( self . reactos_src )
# read the index from the reactos tree
self . reactos_index = self . reactos_repo . index
self . reactos_index . read ( )
# get the actual state for the asked module
self . module = module
with open ( module + ' .cfg ' , ' r ' ) as file_input :
2020-09-21 13:27:21 +00:00
self . module_cfg = yaml . safe_load ( file_input )
2020-01-04 01:14:02 +00:00
self . staged_patch_dir = os . path . join ( ' sdk ' , ' tools ' , ' winesync ' , self . module + ' _staging ' )
def create_or_checkout_wine_branch ( self , wine_tag , wine_staging_tag ) :
wine_branch_name = ' winesync- ' + wine_tag + ' - ' + wine_staging_tag
branch = self . wine_repo . lookup_branch ( wine_branch_name )
if branch is None :
# get our target commits
wine_target_commit = self . wine_repo . revparse_single ( wine_tag )
if isinstance ( wine_target_commit , pygit2 . Tag ) :
wine_target_commit = wine_target_commit . target
wine_staging_target_commit = self . wine_staging_repo . revparse_single ( wine_staging_tag )
if isinstance ( wine_staging_target_commit , pygit2 . Tag ) :
wine_staging_target_commit = wine_staging_target_commit . target
self . wine_repo . branches . local . create ( wine_branch_name , self . wine_repo . revparse_single ( ' HEAD ' ) )
self . wine_repo . checkout ( self . wine_repo . lookup_branch ( wine_branch_name ) )
self . wine_repo . reset ( wine_target_commit , pygit2 . GIT_RESET_HARD )
# do the same for the wine-staging tree
self . wine_staging_repo . branches . local . create ( wine_branch_name , self . wine_staging_repo . revparse_single ( ' HEAD ' ) )
self . wine_staging_repo . checkout ( self . wine_staging_repo . lookup_branch ( wine_branch_name ) )
self . wine_staging_repo . reset ( wine_staging_target_commit , pygit2 . GIT_RESET_HARD )
# run the wine-staging script
subprocess . call ( [ ' bash ' , ' -c ' , self . wine_staging_src + ' /patches/patchinstall.sh DESTDIR= ' + self . wine_src + ' --all --backend=git-am ' ] )
# delete the branch we created
self . wine_staging_repo . checkout ( self . wine_staging_repo . lookup_branch ( ' master ' ) )
self . wine_staging_repo . branches . delete ( wine_branch_name )
else :
self . wine_repo . checkout ( self . wine_repo . lookup_branch ( wine_branch_name ) )
return wine_branch_name
# helper function for resolving wine tree path to reactos one
# Note : it doesn't care about the fact that the file actually exists or not
def wine_to_reactos_path ( self , wine_path ) :
if wine_path in self . module_cfg [ ' files ' ] :
# we have a direct mapping
return self . module_cfg [ ' files ' ] [ wine_path ]
if not ' / ' in wine_path :
# root files should have a direct mapping
return None
wine_dir , wine_file = os . path . split ( wine_path )
if wine_dir in self . module_cfg [ ' directories ' ] :
# we have a mapping for the directory
return os . path . join ( self . module_cfg [ ' directories ' ] [ wine_dir ] , wine_file )
# no match
return None
def sync_wine_commit ( self , wine_commit , in_staging , staging_patch_index ) :
# Get the diff object
diff = self . wine_repo . diff ( wine_commit . parents [ 0 ] , wine_commit )
2020-09-11 17:09:22 +00:00
modified_files = False
2020-01-04 01:14:02 +00:00
ignored_files = [ ]
warning_message = ' '
complete_patch = ' '
if in_staging :
# see if we already applied this patch
patch_file_name = f ' { staging_patch_index : 04 } - { string_to_valid_file_name ( wine_commit . message . splitlines ( ) [ 0 ] ) } .diff '
patch_path = os . path . join ( self . reactos_src , self . staged_patch_dir , patch_file_name )
if os . path . isfile ( patch_path ) :
print ( f ' Skipping patch as { patch_path } already exists ' )
return True , ' '
for delta in diff . deltas :
2020-09-11 17:09:22 +00:00
if delta . status == pygit2 . GIT_DELTA_ADDED :
2020-01-04 01:14:02 +00:00
# check if we should care
new_reactos_path = self . wine_to_reactos_path ( delta . new_file . path )
if not new_reactos_path is None :
warning_message + = ' file ' + delta . new_file . path + ' is added to the wine tree ! \n '
old_reactos_path = ' /dev/null '
else :
old_reactos_path = None
2020-09-11 17:09:22 +00:00
elif delta . status == pygit2 . GIT_DELTA_DELETED :
2020-01-04 01:14:02 +00:00
# check if we should care
old_reactos_path = self . wine_to_reactos_path ( delta . old_file . path )
if not old_reactos_path is None :
warning_message + = ' file ' + delta . old_file . path + ' is removed from the wine tree ! \n '
new_reactos_path = ' /dev/null '
else :
new_reactos_path = None
elif delta . new_file . path . endswith ( ' Makefile.in ' ) :
warning_message + = ' file ' + delta . new_file . path + ' was modified ! \n '
# no need to warn that those are ignored, we just did.
continue
else :
new_reactos_path = self . wine_to_reactos_path ( delta . new_file . path )
old_reactos_path = self . wine_to_reactos_path ( delta . old_file . path )
if ( new_reactos_path is not None ) or ( old_reactos_path is not None ) :
# print('Must apply diff: ' + old_reactos_path + ' --> ' + new_reactos_path)
2020-09-11 17:09:22 +00:00
if delta . status == pygit2 . GIT_DELTA_ADDED :
new_blob = self . wine_repo . get ( delta . new_file . id )
blob_patch = pygit2 . Patch . create_from (
old = None ,
new = new_blob ,
new_as_path = new_reactos_path )
elif delta . status == pygit2 . GIT_DELTA_DELETED :
old_blob = self . wine_repo . get ( delta . old_file . id )
blob_patch = pygit2 . Patch . create_from (
old = old_blob ,
new = None ,
old_as_path = old_reactos_path )
else :
new_blob = self . wine_repo . get ( delta . new_file . id )
old_blob = self . wine_repo . get ( delta . old_file . id )
2020-01-04 01:14:02 +00:00
2020-09-11 17:09:22 +00:00
blob_patch = pygit2 . Patch . create_from (
old = old_blob ,
new = new_blob ,
old_as_path = old_reactos_path ,
new_as_path = new_reactos_path )
2020-01-04 01:14:02 +00:00
# print(str(wine_commit.id))
# print(blob_patch.text)
# this doesn't work
# reactos_diff = pygit2.Diff.parse_diff(blob_patch.text)
# reactos_repo.apply(reactos_diff)
try :
subprocess . run ( [ ' git ' , ' -C ' , self . reactos_src , ' apply ' , ' --reject ' ] , input = blob_patch . data , check = True )
except subprocess . CalledProcessError as err :
warning_message + = ' Error while applying patch to ' + new_reactos_path + ' \n '
2020-09-11 17:09:22 +00:00
if delta . status == pygit2 . GIT_DELTA_DELETED :
2022-03-13 23:03:40 +00:00
try :
self . reactos_index . remove ( old_reactos_path )
except IOError as err :
warning_message + = ' Error while removing file ' + old_reactos_path + ' \n '
2020-12-08 17:10:27 +00:00
# here we check if the file exists. We don't complain, because applying the patch already failed anyway
elif os . path . isfile ( os . path . join ( self . reactos_src , new_reactos_path ) ) :
2020-09-11 17:09:22 +00:00
self . reactos_index . add ( new_reactos_path )
2020-01-04 01:14:02 +00:00
complete_patch + = blob_patch . text
2020-09-11 17:09:22 +00:00
modified_files = True
2020-01-04 01:14:02 +00:00
else :
ignored_files + = [ delta . old_file . path , delta . new_file . path ]
if not modified_files :
# We applied nothing
return False , ' '
print ( ' Applied patches from wine commit ' + str ( wine_commit . id ) )
if ignored_files :
warning_message + = ' WARNING : some files were ignored: ' + ' ' . join ( ignored_files ) + ' \n '
if not in_staging :
self . module_cfg [ ' tags ' ] [ ' wine ' ] = str ( wine_commit . id )
with open ( self . module + ' .cfg ' , ' w ' ) as file_output :
yaml . dump ( self . module_cfg , file_output )
self . reactos_index . add ( f ' sdk/tools/winesync/ { self . module } .cfg ' )
else :
# Add the staging patch
# do not save the wine commit ID in <module>.cfg, as it's a local one for staging patches
if not os . path . isdir ( os . path . join ( self . reactos_src , self . staged_patch_dir ) ) :
os . mkdir ( os . path . join ( self . reactos_src , self . staged_patch_dir ) )
with open ( patch_path , ' w ' ) as file_output :
file_output . write ( complete_patch )
self . reactos_index . add ( os . path . join ( self . staged_patch_dir , patch_file_name ) )
self . reactos_index . write ( )
commit_msg = f ' [WINESYNC] { wine_commit . message } \n '
if ( in_staging ) :
commit_msg + = f ' wine-staging patch by { wine_commit . author . name } < { wine_commit . author . email } > '
else :
commit_msg + = f ' wine commit id { str ( wine_commit . id ) } by { wine_commit . author . name } < { wine_commit . author . email } > '
self . reactos_repo . create_commit ( ' HEAD ' ,
pygit2 . Signature ( ' winesync ' , ' ros-dev@reactos.org ' ) ,
self . reactos_repo . default_signature ,
commit_msg ,
self . reactos_index . write_tree ( ) ,
[ self . reactos_repo . head . target ] )
if ( warning_message != ' ' ) :
warning_message + = ' If needed, amend the current commit in your reactos tree and start this script again '
if not in_staging :
2020-09-11 17:09:22 +00:00
warning_message + = f ' \n ' \
f ' You can see the details of the wine commit here: \n ' \
f ' https://source.winehq.org/git/wine.git/commit/ { str ( wine_commit . id ) } \n '
2020-01-04 01:14:02 +00:00
else :
2020-09-11 17:09:22 +00:00
warning_message + = f ' \n ' \
f ' Do not forget to run \n ' \
f ' git diff HEAD^ \' :(exclude)sdk/tools/winesync/ { patch_file_name } \' > sdk/tools/winesync/ { patch_file_name } \n ' \
f ' after your correction and then \n ' \
f ' git add sdk/tools/winesync/ { patch_file_name } \n ' \
f ' before running " git commit --amend " '
2020-01-04 01:14:02 +00:00
return True , warning_message
def revert_staged_patchset ( self ) :
# revert all of this in one commmit
staged_patch_dir_path = os . path . join ( self . reactos_src , self . staged_patch_dir )
if not os . path . isdir ( staged_patch_dir_path ) :
return True
has_patches = False
for patch_file_name in sorted ( os . listdir ( staged_patch_dir_path ) , reverse = True ) :
patch_path = os . path . join ( staged_patch_dir_path , patch_file_name )
if not os . path . isfile ( patch_path ) :
continue
has_patches = True
with open ( patch_path , ' rb ' ) as patch_file :
try :
subprocess . run ( [ ' git ' , ' -C ' , self . reactos_src , ' apply ' , ' -R ' , ' --reject ' ] , stdin = patch_file , check = True )
except subprocess . CalledProcessError as err :
print ( f ' Error while reverting patch { patch_file_name } ' )
print ( ' Please check, remove the offending patch with git rm, and relaunch this script ' )
return False
self . reactos_index . remove ( os . path . join ( self . staged_patch_dir , patch_file_name ) )
self . reactos_index . write ( )
os . remove ( patch_path )
if not has_patches :
return True
self . reactos_index . add_all ( [ f for f in self . module_cfg [ ' files ' ] . values ( ) ] )
self . reactos_index . add_all ( [ f ' { d } /*.* ' for d in self . module_cfg [ ' directories ' ] . values ( ) ] )
self . reactos_index . write ( )
self . reactos_repo . create_commit (
' HEAD ' ,
self . reactos_repo . default_signature ,
self . reactos_repo . default_signature ,
f ' [WINESYNC]: revert wine-staging patchset for { self . module } ' ,
self . reactos_index . write_tree ( ) ,
[ self . reactos_repo . head . target ] )
return True
def sync_to_wine ( self , wine_tag , wine_staging_tag ) :
# Get our target commit
wine_target_commit = self . wine_repo . revparse_single ( wine_tag )
if isinstance ( wine_target_commit , pygit2 . Tag ) :
wine_target_commit = wine_target_commit . target
# print(f'wine target commit is {wine_target_commit}')
# get the wine commit id where we left
in_staging = False
wine_last_sync = self . wine_repo . revparse_single ( self . module_cfg [ ' tags ' ] [ ' wine ' ] )
if ( isinstance ( wine_last_sync , pygit2 . Tag ) ) :
if not self . revert_staged_patchset ( ) :
return
wine_last_sync = wine_last_sync . target
if ( isinstance ( wine_last_sync , pygit2 . Commit ) ) :
wine_last_sync = wine_last_sync . id
# create a branch to keep things clean
wine_branch_name = self . create_or_checkout_wine_branch ( wine_tag , wine_staging_tag )
finished_sync = True
staging_patch_index = 1
# walk each commit between last sync and the asked tag/revision
wine_commit_walker = self . wine_repo . walk ( self . wine_repo . head . target , pygit2 . GIT_SORT_TOPOLOGICAL | pygit2 . GIT_SORT_REVERSE )
wine_commit_walker . hide ( wine_last_sync )
for wine_commit in wine_commit_walker :
applied_patch , warning_message = self . sync_wine_commit ( wine_commit , in_staging , staging_patch_index )
if str ( wine_commit . id ) == str ( wine_target_commit ) :
print ( ' We are now in staging territory ' )
in_staging = True
if not applied_patch :
continue
if in_staging :
staging_patch_index + = 1
if warning_message != ' ' :
2020-09-11 17:09:22 +00:00
print ( " THERE WERE SOME ISSUES WHEN APPLYING THE PATCH \n \n " )
2020-01-04 01:14:02 +00:00
print ( warning_message )
2020-09-11 17:09:22 +00:00
print ( " \n " )
2020-01-04 01:14:02 +00:00
finished_sync = False
break
# we're done without error
if finished_sync :
# update wine tag and commit
self . module_cfg [ ' tags ' ] [ ' wine ' ] = wine_tag
with open ( self . module + ' .cfg ' , ' w ' ) as file_output :
yaml . dump ( self . module_cfg , file_output )
self . reactos_index . add ( f ' sdk/tools/winesync/ { self . module } .cfg ' )
self . reactos_index . write ( )
self . reactos_repo . create_commit (
' HEAD ' ,
self . reactos_repo . default_signature ,
self . reactos_repo . default_signature ,
f ' [WINESYNC]: { self . module } is now in sync with wine-staging { wine_tag } ' ,
self . reactos_index . write_tree ( ) ,
[ self . reactos_repo . head . target ] )
print ( ' The branch ' + wine_branch_name + ' was created in your wine repository. You might want to delete it, but you should keep it in case you want to sync more module up to this wine version ' )
def main ( ) :
parser = argparse . ArgumentParser ( )
parser . add_argument ( ' module ' , help = ' The module you want to sync. <module>.cfg must exist in the current directory ' )
parser . add_argument ( ' wine_tag ' , help = ' The wine tag or commit id to sync to ' )
parser . add_argument ( ' wine_staging_tag ' , help = ' The wine staging tag or commit id to pick wine staged patches from ' )
args = parser . parse_args ( )
syncator = wine_sync ( args . module )
return syncator . sync_to_wine ( args . wine_tag , args . wine_staging_tag )
if __name__ == ' __main__ ' :
2020-09-11 17:09:22 +00:00
main ( )