# ---------------------------------------------- # ~{ GitHub Integration }~ # [Author] Nicolò "fenix" Santilio # [github] fenix-hub/godot-engine.github-integration # [version] 0.2.9 # [date] 09.13.2019 # Committing this repo I always need to delete all .import files # and don't import this gitignore. # ----------------------------------------------- tool extends Control onready var _message = $VBoxContainer2/HBoxContainer7/message #onready var _file = $VBoxContainer/HBoxContainer/file onready var _branch = $VBoxContainer2/HBoxContainer2/branch onready var repository = $VBoxContainer2/HBoxContainer/repository onready var Loading = $VBoxContainer2/loading2 onready var Gitignore = $VBoxContainer2/HBoxContainer3/VBoxContainer2/gitignore onready var gitignoreBtn = $VBoxContainer2/HBoxContainer3/VBoxContainer2/girignorebuttons/gitignoreBtn onready var about_gitignoreBtn = $VBoxContainer2/HBoxContainer3/VBoxContainer2/girignorebuttons/about_gitignoreBtn onready var SelectFiles = $ChooseFile onready var selectfilesBtn = $VBoxContainer2/HBoxContainer3/VBoxContainer/HBoxContainer/choosefilesBtn onready var removefileBtn = $VBoxContainer2/HBoxContainer3/VBoxContainer/HBoxContainer/removefileBtn onready var selectdirectoryBtn = $VBoxContainer2/HBoxContainer3/VBoxContainer/HBoxContainer/choosedirectoryBtn onready var Progress = $VBoxContainer2/ProgressBar onready var Uncommitted = $VBoxContainer2/HBoxContainer3/VBoxContainer/uncommitted enum REQUESTS { UPLOAD = 0, UPDATE = 1, BLOB = 2 , LATEST_COMMIT = 4, BASE_TREE = 5, NEW_TREE = 8, NEW_COMMIT = 6, PUSH = 7, COMMIT = 9, LFS = 10, POST_LFS = 11, END = -1 } var requesting var new_repo = HTTPRequest.new() var repo_selected var branches = [] var branches_contents = [] var branch_idx = 0 var files : Array = [] var directories = [] onready var error = $VBoxContainer2/error var sha_latest_commit var sha_base_tree var sha_new_tree var sha_new_commit var list_file_sha = [] var list_file_size = [] var lfs = [] var gitignore_file : Dictionary const DIRECTORY : String = "res://" var IGNORE_FILES : PoolStringArray = [] var IGNORE_FOLDERS : PoolStringArray = [] var current_handled_file : String signal blob_created() signal latest_commit() signal base_tree() signal new_commit() signal new_tree() signal file_blobbed() signal file_committed() signal pushed() signal files_filtered() signal lfs() signal lfs_push() func _ready(): new_repo.use_threads = true connect_signals() Loading.hide() call_deferred("add_child",new_repo) error.hide() removefileBtn.set_disabled(true) func set_darkmode(darkmode : bool): if darkmode: $BG.color = "#24292e" set_theme(load("res://addons/github-integration/resources/themes/GitHubTheme-Dark.tres")) else: $BG.color = "#f6f8fa" set_theme(load("res://addons/github-integration/resources/themes/GitHubTheme.tres")) func connect_signals(): new_repo.connect("request_completed",self,"request_completed") _branch.connect("item_selected",self,"selected_branch") gitignoreBtn.connect("toggled",self,"on_gitignore_toggled") selectfilesBtn.connect("pressed",self,"on_selectfiles_pressed") # SelectFiles.connect("confirmed",self,"on_confirm") SelectFiles.connect("files_selected",self,"on_files_selected") Uncommitted.connect("item_selected",self,"on_item_selected") Uncommitted.connect("multi_selected",self,"on_multiple_item_selected") removefileBtn.connect("pressed",self,"on_removefile_pressed") Uncommitted.connect("nothing_selected",self,"on_nothing_selected") SelectFiles.connect("dir_selected",self,"on_dir_selected") selectdirectoryBtn.connect("pressed",self,"on_selectdirectory_pressed") about_gitignoreBtn.connect("pressed",self,"about_gitignore_pressed") func request_completed(result, response_code, headers, body ): get_parent().print_debug_message("REQUEST TO API : Request exited with code %s" % response_code) if response_code == 422: get_parent().print_debug_message(JSON.parse(body.get_string_from_utf8()).result) if result == 0: match requesting: REQUESTS.UPLOAD: if response_code == 201: hide() get_parent().print_debug_message("commited and pushed...") get_parent().UserPanel.request_repositories(get_parent().UserPanel.REQUESTS.UP_REPOS) elif response_code == 422: error.text = "Error: "+JSON.parse(body.get_string_from_utf8()).result.errors[0].message error.show() REQUESTS.UPDATE: if response_code == 200: pass REQUESTS.COMMIT: if response_code == 201: get_parent().print_debug_message("file committed!") get_parent().print_debug_message(" ") emit_signal("file_committed") if response_code == 200: get_parent().print_debug_message("file updated!") get_parent().print_debug_message(" ") emit_signal("file_committed") if response_code == 422: get_parent().print_debug_message("file already exists, skipping...") get_parent().print_debug_message(" ") emit_signal("file_committed") REQUESTS.LATEST_COMMIT: if response_code == 200: sha_latest_commit = JSON.parse(body.get_string_from_utf8()).result.object.sha get_parent().print_debug_message("got last commit") emit_signal("latest_commit") REQUESTS.BASE_TREE: if response_code == 200: sha_base_tree = JSON.parse(body.get_string_from_utf8()).result.tree.sha get_parent().print_debug_message("got base tree") emit_signal("base_tree") REQUESTS.BLOB: match response_code: 201: list_file_sha.append(JSON.parse(body.get_string_from_utf8()).result.sha) get_parent().print_debug_message("blobbed file") # OS.delay_msec(1000) emit_signal("file_blobbed") 400: list_file_sha.append("no-sha") get_parent().print_debug_message("could not blob this file due to reading errors, skipping...",1) emit_signal("file_blobbed") 502: list_file_sha.append("no-sha") get_parent().print_debug_message("could not blob this file due to server errors, skipping...",1) emit_signal("file_blobbed") REQUESTS.NEW_TREE: match response_code: 201: sha_new_tree = JSON.parse(body.get_string_from_utf8()).result.sha get_parent().print_debug_message("created new tree of files") emit_signal("new_tree") 422: get_parent().print_debug_message("could not process the new tree, the file may be corrupted or already present in the repository without any changes.") emit_signal("new_tree") REQUESTS.NEW_COMMIT: match response_code: 201: sha_new_commit = JSON.parse(body.get_string_from_utf8()).result.sha get_parent().print_debug_message("created new commit") emit_signal("new_commit") 422: get_parent().print_debug_message("could not process the new commit, tree may be invalid.") emit_signal("new_commit") REQUESTS.PUSH: if response_code == 200: get_parent().print_debug_message("pushed and committed with success!") if not lfs.size(): get_parent().loading(false) Loading.hide() emit_signal("pushed") REQUESTS.LFS: if response_code == 200: var object = JSON.parse(body.get_string_from_utf8()).result # print(response_code," ",JSON.parse(body.get_string_from_utf8()).result) lfs.clear() lfs = object.objects as Array get_parent().print_debug_message("posted all git lfs files, now uploading...") emit_signal("lfs") REQUESTS.POST_LFS: # if response_code == 200: get_parent().print_debug_message(response_code+" "+JSON.parse(body.get_string_from_utf8()).result) emit_signal("lfs_push") get_parent().loading(false) Loading.hide() func load_branches(br : Array, s_r : Dictionary, ct : Array, gitignore : Dictionary) : _branch.clear() repo_selected = s_r branches_contents = ct branches = br for branch in branches: _branch.add_item(branch.name) gitignore_file = gitignore if gitignore: Gitignore.set_text(Marshalls.base64_to_utf8(gitignore.content)) else: Gitignore.set_text("") repository.set_text(repo_selected.name+"/"+_branch.get_item_text(branch_idx)) func selected_branch(id : int): branch_idx = id repository.set_text(repo_selected.name+"/"+_branch.get_item_text(branch_idx)) #update_gitignore() # |---------------------------------------------------------| func _on_Button_pressed(): # Loading.show() get_parent().loading(true) load_gitignore() get_parent().print_debug_message("fetching all files in project...") request_sha_latest_commit() #func update_gitignore(): # var gitignore_filepath = UserData.directory+repo_selected.name+"/"+_branch.get_item_text(branch_idx)+"/" # var ignorefile = File.new() # var error = ignorefile.open(gitignore_filepath+".gitignore",File.WRITE) # if error: # Gitignore.set_text(ignorefile.get_as_text()) # ignorefile.close() # ------- gitignore ---- func load_gitignore(): list_file_size.clear() var gitignore_filepath = UserData.directory+repo_selected.name+"/"+_branch.get_item_text(branch_idx)+"/" var dir = Directory.new() if not dir.dir_exists(gitignore_filepath): dir.open(UserData.directory) dir.make_dir_recursive(gitignore_filepath) get_parent().print_debug_message("made directory in user folder for this .gitignore file, at %s"%gitignore_filepath) var ignorefile = File.new() var error = ignorefile.open(gitignore_filepath+".gitignore",File.WRITE) for line in range(0,Gitignore.get_line_count()): var gitline = Gitignore.get_line(line) ignorefile.store_line(gitline) if gitline.begins_with("*"): IGNORE_FILES.append(gitline.lstrip("*.")) elif gitline.ends_with("/"): IGNORE_FOLDERS.append(gitline.rstrip("/")) elif gitline.begins_with("#") or gitline.begins_with("!"): pass ignorefile.close() # load the gitignore files.push_front(gitignore_filepath+".gitignore") # load the gitattributes if File.new().file_exists(UserData.directory+repo_selected.name+"/"+_branch.get_item_text(branch_idx)+"/.gitattributes"): files.push_front(UserData.directory+repo_selected.name+"/"+_branch.get_item_text(branch_idx)+"/.gitattributes") lfs.clear() var filtered_files : Array = [] for file in files: var filter_file = false if file in IGNORE_FILES or file.get_file() in IGNORE_FILES or file.get_extension() in IGNORE_FILES: continue else: for folder in IGNORE_FOLDERS: if file.get_base_dir() == folder or folder in file.get_base_dir(): filter_file = true if not filter_file: filtered_files.append(file) var size_file = File.new() size_file.open(file,File.READ) list_file_size.append(size_file.get_len()) size_file.close() files.clear() files = filtered_files # files.push_front(gitignore_filepath+".gitignore") emit_signal("files_filtered") # |---------------------------------------------------------| func request_sha_latest_commit(): requesting = REQUESTS.LATEST_COMMIT new_repo.request("https://api.github.com/repos/"+repo_selected.owner.login+"/"+repo_selected.name+"/git/refs/heads/"+_branch.get_item_text(branch_idx),UserData.header,false,HTTPClient.METHOD_GET,"") yield(self,"latest_commit") request_base_tree() func request_base_tree(): requesting = REQUESTS.BASE_TREE new_repo.request("https://api.github.com/repos/"+repo_selected.owner.login+"/"+repo_selected.name+"/git/commits/"+sha_latest_commit,UserData.header,false,HTTPClient.METHOD_GET,"") yield(self,"base_tree") request_blobs() func request_blobs(): requesting = REQUESTS.BLOB list_file_sha.clear() for file in files: current_handled_file = file if list_file_size[files.find(file)] < 104857600: var content = "" var sha = "" # is set to update a file var encoding = "" var f : File = File.new() f.open(file,File.READ) content = Marshalls.raw_to_base64(f.get_buffer(f.get_len())) encoding = "base64" f.close() get_parent().print_debug_message("blobbing ~> "+file.get_file()) var bod = { "content":content, "encoding":encoding, } new_repo.request("https://api.github.com/repos/"+repo_selected.owner.login+"/"+repo_selected.name+"/git/blobs", UserData.header,false,HTTPClient.METHOD_POST,JSON.print(bod)) yield(self,"file_blobbed") else: get_parent().print_debug_message("pointing large file, please wait...") var output = [] OS.execute( 'git', [ "lfs", "pointer",'--file',ProjectSettings.globalize_path(file)], true, output ) var oid : String = output[0].split(":",false)[2] var onlyoid : String = oid.rstrip("size").split(" ")[0].replace("\nsize","") list_file_sha.append(onlyoid) Progress.set_value(range_lerp(files.find(file),0,files.size(),0,100)) get_parent().print_debug_message("blobbed each file with success, start committing...") Progress.set_value(100) request_commit_tree() func request_commit_tree(): requesting = REQUESTS.NEW_TREE var tree = [] for file in files: if list_file_sha[files.find(file)] != "no-sha": pass else: continue var file_name : String = files[files.find(file)].right((DIRECTORY).length()) if file_name.begins_with("/"): file_name = file_name.lstrip("/") if list_file_size[files.find(file)] < 104857600: if file.get_file() == ".gitignore": tree.append({ "path":".gitignore", "mode":"100644", "type":"blob", "sha":list_file_sha[files.find(file)], }) elif file.get_file() == ".gitattributes": tree.append({ "path":".gitattributes", "mode":"100644", "type":"blob", "sha":list_file_sha[files.find(file)], }) else: tree.append({ "path":file_name, "mode":"100644", "type":"blob", "sha":list_file_sha[files.find(file)], }) else: lfs.append({"oid": list_file_sha[files.find(file)],"size": list_file_size[files.find(file)]}) var bod : Dictionary = { "base_tree": sha_base_tree, "tree":tree } new_repo.request("https://api.github.com/repos/"+repo_selected.owner.login+"/"+repo_selected.name+"/git/trees",UserData.header,false,HTTPClient.METHOD_POST,JSON.print(bod)) yield(self,"new_tree") request_new_commit() func request_new_commit(): requesting = REQUESTS.NEW_COMMIT var message = _message.text var bod = { "parents": [sha_latest_commit], "tree": sha_new_tree, "message": message } new_repo.request("https://api.github.com/repos/"+repo_selected.owner.login+"/"+repo_selected.name+"/git/commits",UserData.header,false,HTTPClient.METHOD_POST,JSON.print(bod)) yield(self,"new_commit") request_push_commit() func request_push_commit(): requesting = REQUESTS.PUSH var bod = { "sha": sha_new_commit } new_repo.request("https://api.github.com/repos/"+repo_selected.owner.login+"/"+repo_selected.name+"/git/refs/heads/"+_branch.get_item_text(branch_idx),UserData.header,false,HTTPClient.METHOD_POST,JSON.print(bod)) yield(self,"pushed") if lfs.size() > 0: requesting = REQUESTS.LFS var body = {"operation": "upload","ref": {"name":"refs/heads/"+_branch.get_item_text(branch_idx)},"transfers": [ "basic" ],"objects": lfs} new_repo.request("https://github.com/"+repo_selected.owner.login+"/"+repo_selected.name+UserData.gitlfs_request,UserData.gitlfs_header,false,HTTPClient.METHOD_POST,JSON.print(body)) yield(self,"lfs") if lfs.size() > 0: requesting = REQUESTS.POST_LFS get_parent().print_debug_message(lfs) var body = { "transfer":"basic" , "objects":lfs} new_repo.request("https://github.com/"+repo_selected.owner.login+"/"+repo_selected.name+UserData.gitlfs_request,UserData.gitlfs_header,false,HTTPClient.METHOD_PUT,JSON.print(body)) yield(self,"lfs_push") empty_fileds() Progress.set_value(0) get_parent().Repo._on_reload_pressed() # --------------------------------------@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ func _on_loading2_visibility_changed(): var Mat = Loading.get_material() if Loading.visible: Mat.set_shader_param("speed",5) else: Mat.set_shader_param("speed",0) func _on_close2_pressed(): empty_fileds() hide() get_parent().Repo.show() func empty_fileds(): files.clear() directories.clear() sha_latest_commit = "" sha_base_tree = "" sha_new_tree = "" sha_new_commit = "" list_file_sha.clear() IGNORE_FILES.resize(0) IGNORE_FOLDERS.resize(0) _message.text = "" Uncommitted.clear() # hide() # get_parent().Repo.show() func on_gitignore_toggled(toggle : bool): Gitignore.set_readonly(!toggle) func on_confirm(): pass func on_files_selected(paths : PoolStringArray): for path in paths: if not files.has(path): files.append(path) else: files.erase(path) show_files(paths,true,false) func on_dir_selected(path : String): var directories = [] var dir = Directory.new() dir.open(path) dir.list_dir_begin(true,false) var file = dir.get_next() while (file != ""): if dir.current_is_dir(): var directorypath = dir.get_current_dir()+"/"+file directories.append(directorypath) else: var filepath = dir.get_current_dir()+"/"+file if not files.has(filepath): files.append(filepath) file = dir.get_next() dir.list_dir_end() show_files(files,true,false) for directory in directories: on_dir_selected(directory) func show_files(paths : PoolStringArray, isfile : bool = false , isdir : bool = false): Uncommitted.clear() for file in paths: file = file.replace("///","//") if isfile: Uncommitted.add_item(file,IconLoaderGithub.load_icon_from_name("file-gray")) # if isdir: # for dir in paths: # Uncommitted.add_item(dir,IconLoaderGithub.load_icon_from_name("dir")) func on_removefile_pressed(): var filestoremove = Uncommitted.get_selected_items() if filestoremove.size() == 0: on_nothing_selected() return var first_file = filestoremove[0] var file_name = Uncommitted.get_item_text(first_file) files.erase(file_name) Uncommitted.remove_item(first_file) if Uncommitted.get_selected_items().size() > 0: on_removefile_pressed() else: on_nothing_selected() func on_selectfiles_pressed(): SelectFiles.set_mode(FileDialog.MODE_OPEN_FILES) SelectFiles.invalidate() SelectFiles.popup() func on_selectdirectory_pressed(): SelectFiles.set_mode(FileDialog.MODE_OPEN_DIR) SelectFiles.invalidate() SelectFiles.popup() func on_item_selected(idx : int): removefileBtn.set_disabled(false) func on_multiple_item_selected(idx : int, selected : bool): removefileBtn.set_disabled(false) func on_nothing_selected(): removefileBtn.set_disabled(true) func about_gitignore_pressed(): OS.shell_open("https://git-scm.com/docs/gitignore")