" The purpose of creating this autoload is with functions like git_branch_log
" which opens another 'git' filetype buffer defining such a function in
" after/ftplugin/ft.vim results in a complain about duplicated function since
" anything defined there will be loaded again. See :help autoload.

" Delete the branch on the current line in the output of Git branch -a.
function! git#git_branch_delete() abort
  let line = trim(getline('.'))
  let branch = split(line, "/")
  if len(branch) == 1
    " Handles the case for local branch
    execute "keepalt Git branch -D " . branch[0]
  else
    " Handles the case for remote remotes/remote_name/branch_name
    let remote = branch[1]
    let branch = branch[2]
    execute "keepalt Git push -d " . remote . " " . branch
    execute "keepalt Git branch -D " . branch
  endif
  silent execute "keepalt bw"
endfunction

" Checkout the branch on the current line in the output of Git branch -a.
function! git#git_branch_checkout() abort
  let line = trim(getline('.'))
  let branch = split(line, "/")
  if len(branch) == 1
    " Handles the case for local branch
    silent execute "keepalt Git checkout " . branch[0]
  else
    " Handles the case for remote remotes/remote_name/branch_name
    let remote = join(branch[1:2], "/")
    silent execute "keepalt Git checkout " . remote
    silent execute "keepalt Git checkout -b " . branch[2]
    silent execute "keepalt Git branch --set-upstream-to=" . remote . " " . branch[2]
  endif
  silent execute "keepalt bw"
endfunction

" Populate the command line with git command to rename branch on current line.
" Run Git branch -a first. Not for renaming current branch.
function! git#git_branch_rename() abort
  let branch_to_rename = trim(getline('.'))
  return ":Git branch -m " . branch_to_rename . " "
endfunction

" Get the log of the branch on current line in the output of Git branch -a.
function! git#git_branch_log() abort
  let line = trim(getline('.'))
  let branch = split(line, "/")
  " Close the buffer opened by Git branch -a
  silent execute "bw"
  if len(branch) == 1
    " Handles the case for local branch
    execute "keepalt Git log " . branch[0]
  else
    " Handles the case for remote remotes/remote_name/branch_name
    let remote = join(branch[1:2], "/")
    execute "keepalt Git log " . remote
  endif
endfunction

" Get the log of the branch on current line in the output of Git branch -a.
" This one returns a log where a line has the form <SHA> <commit-msg>. This
" acts as a precursor to what we run cherry-pick on.
function! git#git_branch_log_pretty() abort
  let line = trim(getline('.'))
  let branch = split(line, "/")
  " Close the buffer opened by Git branch -a
  silent execute "bw"
  if len(branch) == 1
    " Handles the case for local branch
    execute "keepalt Git log --pretty=oneline --no-merges " . branch[0]
  else
    " Handles the case for remote remotes/remote_name/branch_name
    let remote = join(branch[1:2], "/")
    execute "keepalt Git log --pretty=oneline --no-merges " . remote
  endif
endfunction

" Run Git cherry-pick on the commit in the line which we are at. To be used
" after running some variation of Git log.
function! git#git_cherry_pick() abort
  " A line in Git log is of the form commit <SHA>
  let line = trim(getline('.'))
  let commit = split(line, " ")
  silent execute "keepalt Git cherry-pick " . commit[1]
endfunction

" Git cherry-pick a range of commits. To be run on output of Git log
" --pretty=oneline. See git_branch_log_pretty.
function! git#git_cherry_pick_range() abort
  let [lnum1, col1] = getpos("'<")[1:2]
  let [lnum2, col2] = getpos("'>")[1:2]
  let lines = reverse(getline(lnum1, lnum2))
  if len(lines) == 0
    return ''
  endif
  let oldCommit = split(lines[0], " ")[:0][0]
  let newCommit = split(lines[-1], " ")[:0][0]
  execute "keepalt Git cherry-pick " . oldCommit . "^.." . newCommit
  silent execute "keepalt bw"
endfunction

" Run Git diff on the commit in the line which we are at. To be used after
" running some variation of Git log.
function! git#git_diff_commit() abort
  " A line in Git log is of the form commit <SHA>
  let line = trim(getline('.'))
  let commit = split(line, " ")
  execute "Gtabedit | Git diff " . commit[1] . "^ " . commit[1] . "| only"
endfunction

" Run Git difftool on the commit in the line which we are at. To be used after
" running some variation of Git log.
function! git#git_difftool_commit() abort
  " A line in Git log is of the form commit <SHA>
  let line = trim(getline('.'))
  let commit = split(line, " ")
  execute "Gtabedit | Git difftool " . commit[1] . "^ " . commit[1]
endfunction

" To be used after running some variation of Git log.
function! git#git_format_patch() abort
  " A line in Git log is of the form commit <SHA>
  let line = trim(getline('.'))
  let commit = split(line, " ")
  execute "keepalt Git format-patch " . commit[1] . "~1"
endfunction

" Git format-patch a range of commits. To be run on output of Git log
" --pretty=oneline. See git_branch_log_pretty.
function! git#git_format_patch_range() abort
  let [lnum1, col1] = getpos("'<")[1:2]
  let [lnum2, col2] = getpos("'>")[1:2]
  let lines = reverse(getline(lnum1, lnum2))
  if len(lines) == 0
    return ''
  endif
  let oldCommit = split(lines[0], " ")[:0][0]
  let newCommit = split(lines[-1], " ")[:0][0]
  execute "keepalt Git format-patch " . oldCommit . "~1.." . newCommit
  silent execute "keepalt bw"
endfunction

" The next two functions allow scoping diffs by line range of a file and named
" block in a file. Inspired by reading the following article.
" https://susanpotter.net/software/tracking-diffs-by-scoping-to-file-range-function-method-or-class-changes-in-git/

" Use these two links as reference to come up with this function
" https://vi.stackexchange.com/questions/17606/vmap-and-visual-block-how-do-i-write-a-function-to-operate-once-for-the-entire
" https://stackoverflow.com/questions/41238238/how-to-map-vim-visual-mode-to-replace-my-selected-text-parts
function! git#git_log_range() abort
  let [lnum1, col1] = getpos("'<")[1:2]
  let [lnum2, col2] = getpos("'>")[1:2]
  silent execute "keepalt Git log --no-patch -L " . lnum1 . "," . lnum2 . ":%"
endfunction

" Taken from https://learnvimscriptthehardway.stevelosh.com/chapters/33.html.
" Modified and stripped to do what I needed.
function! git#git_log_named_block() abort
  normal! `<v`>y
  silent execute "keepalt Git log --no-patch -L :" . trim(shellescape(@@)) . ":%"
endfunction

" Compares current branch against master. For eg. git log master..current.
" This combined with Git diff are helpful for MR reviews.
function! git#git_log_compare() abort
  let default = split(trim(system('git symbolic-ref refs/remotes/origin/HEAD')), '/')
  let current = trim(system('git branch --show-current'))
  let are_we_on_default = match(default[3], current)
  if (are_we_on_default == 0)
    echom "We are already on default branch. Nothing to compare."
  else
    silent execute "keepalt Git log " . default[3] . ".." . current
  endif
endfunction

" Merge remote origin:master/main into local:master/main
function! git#git_fetch_origin_merge() abort
  let default = split(trim(system('git symbolic-ref refs/remotes/origin/HEAD')), '/')
  execute "Git fetch origin " . default[3] . ":" . default[3]
endfunction

" Figures out whether default branch is main or master and runs git merge.
function! git#git_merge_origin() abort
  let default = split(trim(system('git symbolic-ref refs/remotes/origin/HEAD')), '/')
  let current = trim(system("git branch --show-current"))
  let are_we_on_default = match(default[3], current)
  if (are_we_on_default == 0)
    echom "Merging origin/" . default[3]
    execute "Git merge origin/" . default[3]
  else
    echom "Not on " . default[3]
  endif
endfunction

function! git#git_push_to_upstream() abort
  let current = trim(system("git branch --show-current"))
  let upstream = split(trim(system('git rev-parse --abbrev-ref ' . current . '@{upstream}')), '/')
  if len(upstream) != 2
    echom "Upstream not set for branch " . current
  else
    execute "Git push -u " . upstream[0] . " " . upstream[1]
  endif
endfunction

function! git#git_push() abort
  let current = trim(system("git branch --show-current"))
  let upstream = split(trim(system('git rev-parse --abbrev-ref ' . current . '@{upstream}')), '/')
  if len(upstream) != 2
    echom "Upstream not set for branch " . current
    return ":Git push -u " . " " . current
  else
    return ":Git push --force-with-lease -u " . upstream[0] . " " . upstream[1]
  endif
endfunction

" To be used after calling Git log and cursor on the commit line.
function! git#git_push_commit() abort
  let line = trim(getline('.'))
  let commit = split(line, " ")[1]
  let current = trim(system("git branch --show-current"))
  let upstream = split(trim(system('git rev-parse --abbrev-ref ' . current . '@{upstream}')), '/')
  if len(upstream) != 2
    echom "Upstream not set for branch " . current
    return ":Git push -u " . " " . current
  else
    return ":Git push -u " . upstream[0] . " " . commit . ":" . upstream[1]
  endif
endfunction

" Rebase the current checked out branch to the branch on the current line.
" Git branch -a should be run before this.
function! git#git_rebase_branch() abort
  let line = trim(getline('.'))
  let branch = split(line, "/")
  if len(branch) == 1
    " Handles the case for local branch
    return ":Git rebase HEAD~" . " --onto " . branch[0]
  else
    " Handles the case for remote remotes/remote_name/branch_name
    let remote = branch[1]
    let branch = branch[2]
    return ":Git rebase HEAD~" . " --onto " . remote . "/" . branch
  endif
endfunction

" Git reflog should be run first before invoking this
function! git#git_reflog_restore() abort
  let line = trim(getline('.'))
  " A line in git reflog is of the form below
  " f6a8c7ed4 HEAD@{73}: rebase (pick): gst-rtmp: Add mux queues to stats gathering
  let sl1 = split(line, ":")
  " sl1[0] will give f6a8c7ed4 HEAD@{73} for above example
  let sl2 = split(sl1[0], " ")
  " sl2[0] will have f6a8c7ed4 and sl2[1] will have HEAD@{73}
  echom "Command executed will be: Git reset --hard " . sl2[1]
  call inputsave()
  let restore = input('Restore to: ' . line . '. Enter y to proceed: ')
  call inputrestore()
  let to_restore = match(restore, 'y')
  if (to_restore == 0)
    execute "keepalt Git reset --hard " . sl2[1]
  else
    echom "Not restoring to " . line
  endif
endfunction

" Figures out whether default branch is main or master and runs git rebase.
function! git#git_rebase_origin() abort
  let default = split(trim(system('git symbolic-ref refs/remotes/origin/HEAD')), '/')
  echom "Rebasing current branch on origin/" . default[3]
  execute "Git rebase origin/" . default[3]
endfunction

" Opens the output of Git difftool main...<current_branch> for review
function! git#git_review() abort
  let default = split(trim(system('git symbolic-ref refs/remotes/origin/HEAD')), '/')
  let current = trim(system("git branch --show-current"))
  execute "Gtabedit | Git difftool " . default[3] . "..." . current
endfunction

" Opens the output of Git difftool main...<current_branch> for review. Each
" file is opened in a tab.
function! git#git_review_fileview() abort
  let default = split(trim(system('git symbolic-ref refs/remotes/origin/HEAD')), '/')
  let current = trim(system("git branch --show-current"))
  execute "Git difftool -y " . default[3] . "..." . current
endfunction

" Generate a helpful name when using Git stash
function! git#git_stash() abort
  let current = trim(system("git branch --show-current"))
  let current_time = strftime("%Y-%b-%d_%H-%M")
  let stash_name = current . "-" . current_time
  execute "Git stash push -m " . stash_name
endfunction
