# Copyright (C) 2020 Chris Liaw # Author: Chris Liaw # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . module GitCli module Delta class GitDeltaError < StandardError; end class VCSItem attr_reader :path, :full, :type def initialize(type, path, full) @type = type @path = path @full = full end # support sort def <=>(val) if val.is_a?(VCSItem) @path <=> val.path else false end end def ==(val) if val.is_a?(VCSItem) @path == val.path else false end end end class NewDir < VCSItem def initialize(path, full) super(:dir, path, full) end def to_s "[N] #{@path}" end end class NewFile < VCSItem def initialize(path, full) super(:file, path, full) end def to_s "[N] #{@path}" end end class ModifiedDir < VCSItem def initialize(path, full) super(:dir, path, full) end def to_s "[M] #{@path}" end end class ModifiedFile < VCSItem def initialize(path, full) super(:file, path, full) end def to_s "[M] #{@path}" end end class DeletedDir < VCSItem def initialize(path, full) super(:dir, path, full) end def to_s "[D] #{@path}" end end class DeletedFile < VCSItem def initialize(path, full) super(:file, path, full) end def to_s "[D] #{@path}" end end class StagedDir < VCSItem def initialize(path, full) super(:dir, path, full) end def to_s "[S] #{@path}" end end class StagedFile < VCSItem def initialize(path, full) super(:file, path, full) end def to_s "[S] #{@path}" end end class ConflictedDir < VCSItem def initialize(path, full) super(:dir, path, full) end def to_s "[C] #{@path}" end end class ConflictedFile < VCSItem def initialize(path, full) super(:file, path, full) end def to_s "[C] #{@path}" end end def status check_vcs cmd = [] cmd << "cd" cmd << @wsPath cmd << "&&" cmd << @vcs.exe_path cmd << "status" cmdln = cmd.join(" ") log_debug "Status : #{cmdln}" res = os_exec(cmdln) do |st, res| if not st.success? raise GitDeltaError, res else res end #if st.success? # [true, res] #else # [false, res] #end end end # status def modified_files check_vcs cmd = [] cmd << "cd" cmd << @wsPath cmd << "&&" cmd << @vcs.exe_path # list only non staged modifications cmd << "diff --name-only --diff-filter=M" cmdln = cmd.join(" ") log_debug "Modified files : #{cmdln}" dirs = [] files = [] res = os_exec(cmdln) do |st, res| if not st.success? raise GitDeltaError, res else res.each_line do |l| l.chomp! full = File.join(@wsPath,l) if File.directory?(full) dirs << ModifiedDir.new(l,full) else files << ModifiedFile.new(l,full) end end #[true, dirs.sort, files.sort] [dirs.sort, files.sort] end end end # modified files def conflicted_files check_vcs cmd = [] cmd << "cd" cmd << @wsPath cmd << "&&" cmd << @vcs.exe_path cmd << "diff --name-only --diff-filter=U" cmdln = cmd.join(" ") log_debug "Conflicted files : #{cmdln}" dirs = [] files = [] res = os_exec(cmdln) do |st, res| if not st.success? raise GitDeltaError, res else res.each_line do |l| l.chomp! full = File.join(@wsPath,l) if File.directory?(full) dirs << ConflictedDir.new(l, full) else files << ConflictedFile.new(l, full) end end [dirs.sort, files.sort] end end end # conflicted files def new_files check_vcs cmd = [] cmd << "cd" cmd << @wsPath cmd << "&&" cmd << @vcs.exe_path cmd << "ls-files --others --exclude-standard --directory" cmdln = cmd.join(" ") log_debug "New Files : #{cmdln}" dirs = [] files = [] res = os_exec(cmdln) do |st, res| if not st.success? raise GitDeltaError, res else res.each_line do |l| l.chomp! full = File.join(@wsPath,l) if File.directory?(full) dirs << NewDir.new(l,full) else files << NewFile.new(l,full) end end [dirs.sort, files.sort] end end end # new_files def deleted_files check_vcs cmd = [] cmd << "cd" cmd << @wsPath cmd << "&&" cmd << @vcs.exe_path cmd << "ls-files -d" cmdln = cmd.join(" ") log_debug "Deleted files : #{cmdln}" dirs = [] files = [] res = os_exec(cmdln) do |st, res| if not st.success? raise GitDeltaError, res else res.each_line do |l| l.chomp! full = File.join(@wsPath,l) if File.directory?(full) dirs << DeletedDir.new(l,full) else files << DeletedFile.new(l,full) end end [dirs.sort, files.sort] end end end # deleted_files def staged_files check_vcs cmd = [] cmd << "cd" cmd << @wsPath cmd << "&&" cmd << @vcs.exe_path cmd << "diff --name-only --cached" cmdln = cmd.join(" ") log_debug "Staged Files : #{cmdln}" dirs = [] files = [] res = os_exec(cmdln) do |st, res| if not st.success? raise GitDeltaError, res else res.each_line do |l| l.chomp! full = File.join(@wsPath,l) if File.directory?(full) dirs << StagedDir.new(l,full) else files << StagedFile.new(l,full) end end [dirs.sort, files.sort] end end end # staged_files def reset_file_changes(path) raise_if_empty(path, "Path cannot be empty for reset file changes operation", GitDeltaError) check_vcs cmd = [] cmd << "cd" cmd << @wsPath cmd << "&&" cmd << @vcs.exe_path cmd << "checkout --" cmd << path cmdln = cmd.join(" ") log_debug "Reset file changes local changes (given file permanent) : #{cmdln}" res = os_exec(cmdln) do |st, res| [st.success?, res] end end # reset file changes def reset_all_changes check_vcs cmd = [] cmd << "cd" cmd << @wsPath cmd << "&&" cmd << @vcs.exe_path cmd << "reset --hard" cmdln = cmd.join(" ") log_debug "Reset all local changes (permanent) : #{cmdln}" res = os_exec(cmdln) do |st, res| [st.success?, res] end end # reset all changes # If remote is ahead of local # git rev-list HEAD..origin/main --count # # if local is ahead of remote # git rev-list origin/main..HEAD --count # def calculate_distance(from, to) check_vcs cmd = [] cmd << "cd" cmd << @wsPath cmd << "&&" cmd << @vcs.exe_path cmd << "rev-list #{from}..#{to} --count" cmdln = cmd.join(" ") log_debug "Calculate distance between two repos : #{cmdln}" res = os_exec(cmdln) do |st, res| [st.success?, res] end end def is_local_ahead_of_remote?(remote_name, branch = "main") st, res = calculate_distance("#{remote_name}/#{branch}", "HEAD") if st res.to_i > 0 else # not push before true end end def is_remote_ahead_of_local?(remote_name, branch = "main") st, res = calculate_distance("HEAD","#{remote_name}/#{branch}") if st res.to_i > 0 else # never pull before? true end end end end