1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-06-15 00:15:32 +02:00

move patch stuff into its own package

This commit is contained in:
Jesse Duffield
2020-08-15 11:18:40 +10:00
parent 291a8e4de0
commit 826d1660c9
13 changed files with 188 additions and 176 deletions

View File

@ -7,13 +7,3 @@ type CommitFile struct {
DisplayString string DisplayString string
Status int // one of 'WHOLE' 'PART' 'NONE' Status int // one of 'WHOLE' 'PART' 'NONE'
} }
const (
// UNSELECTED is for when the commit file has not been added to the patch in any way
UNSELECTED = iota
// WHOLE is for when you want to add the whole diff of a file to the patch,
// including e.g. if it was deleted
WHOLE = iota
// PART is for when you're only talking about specific lines that have been modified
PART
)

View File

@ -16,6 +16,7 @@ import (
"github.com/go-errors/errors" "github.com/go-errors/errors"
gogit "github.com/go-git/go-git/v5" gogit "github.com/go-git/go-git/v5"
"github.com/jesseduffield/lazygit/pkg/commands/patch"
"github.com/jesseduffield/lazygit/pkg/config" "github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/i18n" "github.com/jesseduffield/lazygit/pkg/i18n"
"github.com/jesseduffield/lazygit/pkg/utils" "github.com/jesseduffield/lazygit/pkg/utils"
@ -84,7 +85,7 @@ type GitCommand struct {
removeFile func(string) error removeFile func(string) error
DotGitDir string DotGitDir string
onSuccessfulContinue func() error onSuccessfulContinue func() error
PatchManager *PatchManager PatchManager *patch.PatchManager
// Push to current determines whether the user has configured to push to the remote branch of the same name as the current or not // Push to current determines whether the user has configured to push to the remote branch of the same name as the current or not
PushToCurrent bool PushToCurrent bool
@ -143,7 +144,7 @@ func NewGitCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.Localizer,
PushToCurrent: pushToCurrent, PushToCurrent: pushToCurrent,
} }
gitCommand.PatchManager = NewPatchManager(log, gitCommand.ApplyPatch) gitCommand.PatchManager = patch.NewPatchManager(log, gitCommand.ApplyPatch)
return gitCommand, nil return gitCommand, nil
} }
@ -1038,7 +1039,7 @@ func (c *GitCommand) CherryPickCommits(commits []*Commit) error {
} }
// GetCommitFiles get the specified commit files // GetCommitFiles get the specified commit files
func (c *GitCommand) GetCommitFiles(commitSha string, patchManager *PatchManager) ([]*CommitFile, error) { func (c *GitCommand) GetCommitFiles(commitSha string, patchManager *patch.PatchManager) ([]*CommitFile, error) {
files, err := c.OSCommand.RunCommandWithOutput("git diff-tree --no-commit-id --name-only -r --no-renames %s", commitSha) files, err := c.OSCommand.RunCommandWithOutput("git diff-tree --no-commit-id --name-only -r --no-renames %s", commitSha)
if err != nil { if err != nil {
return nil, err return nil, err
@ -1047,7 +1048,7 @@ func (c *GitCommand) GetCommitFiles(commitSha string, patchManager *PatchManager
commitFiles := make([]*CommitFile, 0) commitFiles := make([]*CommitFile, 0)
for _, file := range strings.Split(strings.TrimRight(files, "\n"), "\n") { for _, file := range strings.Split(strings.TrimRight(files, "\n"), "\n") {
status := UNSELECTED status := patch.UNSELECTED
if patchManager != nil && patchManager.CommitSha == commitSha { if patchManager != nil && patchManager.CommitSha == commitSha {
status = patchManager.GetFileStatus(file) status = patchManager.GetFileStatus(file)
} }

142
pkg/commands/patch/hunk.go Normal file
View File

@ -0,0 +1,142 @@
package patch
import (
"fmt"
"strings"
"github.com/jesseduffield/lazygit/pkg/utils"
)
type PatchHunk struct {
FirstLineIdx int
oldStart int
newStart int
heading string
bodyLines []string
}
func (hunk *PatchHunk) LastLineIdx() int {
return hunk.FirstLineIdx + len(hunk.bodyLines)
}
func newHunk(lines []string, firstLineIdx int) *PatchHunk {
header := lines[0]
bodyLines := lines[1:]
oldStart, newStart, heading := headerInfo(header)
return &PatchHunk{
oldStart: oldStart,
newStart: newStart,
heading: heading,
FirstLineIdx: firstLineIdx,
bodyLines: bodyLines,
}
}
func headerInfo(header string) (int, int, string) {
match := hunkHeaderRegexp.FindStringSubmatch(header)
oldStart := utils.MustConvertToInt(match[1])
newStart := utils.MustConvertToInt(match[2])
heading := match[3]
return oldStart, newStart, heading
}
func (hunk *PatchHunk) updatedLines(lineIndices []int, reverse bool) []string {
skippedNewlineMessageIndex := -1
newLines := []string{}
lineIdx := hunk.FirstLineIdx
for _, line := range hunk.bodyLines {
lineIdx++ // incrementing at the start to skip the header line
if line == "" {
break
}
isLineSelected := utils.IncludesInt(lineIndices, lineIdx)
firstChar, content := line[:1], line[1:]
transformedFirstChar := transformedFirstChar(firstChar, reverse, isLineSelected)
if isLineSelected || (transformedFirstChar == "\\" && skippedNewlineMessageIndex != lineIdx) || transformedFirstChar == " " {
newLines = append(newLines, transformedFirstChar+content)
continue
}
if transformedFirstChar == "+" {
// we don't want to include the 'newline at end of file' line if it involves an addition we're not including
skippedNewlineMessageIndex = lineIdx + 1
}
}
return newLines
}
func transformedFirstChar(firstChar string, reverse bool, isLineSelected bool) string {
if reverse {
if !isLineSelected && firstChar == "+" {
return " "
} else if firstChar == "-" {
return "+"
} else if firstChar == "+" {
return "-"
} else {
return firstChar
}
}
if !isLineSelected && firstChar == "-" {
return " "
}
return firstChar
}
func (hunk *PatchHunk) formatHeader(oldStart int, oldLength int, newStart int, newLength int, heading string) string {
return fmt.Sprintf("@@ -%d,%d +%d,%d @@%s\n", oldStart, oldLength, newStart, newLength, heading)
}
func (hunk *PatchHunk) formatWithChanges(lineIndices []int, reverse bool, startOffset int) (int, string) {
bodyLines := hunk.updatedLines(lineIndices, reverse)
startOffset, header, ok := hunk.updatedHeader(bodyLines, startOffset, reverse)
if !ok {
return startOffset, ""
}
return startOffset, header + strings.Join(bodyLines, "")
}
func (hunk *PatchHunk) updatedHeader(newBodyLines []string, startOffset int, reverse bool) (int, string, bool) {
changeCount := nLinesWithPrefix(newBodyLines, []string{"+", "-"})
oldLength := nLinesWithPrefix(newBodyLines, []string{" ", "-"})
newLength := nLinesWithPrefix(newBodyLines, []string{"+", " "})
if changeCount == 0 {
// if nothing has changed we just return nothing
return startOffset, "", false
}
var oldStart int
if reverse {
oldStart = hunk.newStart
} else {
oldStart = hunk.oldStart
}
var newStartOffset int
// if the hunk went from zero to positive length, we need to increment the starting point by one
// if the hunk went from positive to zero length, we need to decrement the starting point by one
if oldLength == 0 {
newStartOffset = 1
} else if newLength == 0 {
newStartOffset = -1
} else {
newStartOffset = 0
}
newStart := oldStart + startOffset + newStartOffset
newStartOffset = startOffset + newLength - oldLength
formattedHeader := hunk.formatHeader(oldStart, oldLength, newStart, newLength, hunk.heading)
return newStartOffset, formattedHeader, true
}

View File

@ -1,4 +1,4 @@
package commands package patch
import ( import (
"sort" "sort"
@ -7,6 +7,16 @@ import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
const (
// UNSELECTED is for when the commit file has not been added to the patch in any way
UNSELECTED = iota
// WHOLE is for when you want to add the whole diff of a file to the patch,
// including e.g. if it was deleted
WHOLE = iota
// PART is for when you're only talking about specific lines that have been modified
PART
)
type fileInfo struct { type fileInfo struct {
mode int // one of WHOLE/PART mode int // one of WHOLE/PART
includedLineIndices []int includedLineIndices []int

View File

@ -1,160 +1,16 @@
package commands package patch
import ( import (
"fmt" "fmt"
"regexp" "regexp"
"strconv"
"strings" "strings"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
var hunkHeaderRegexp = regexp.MustCompile(`(?m)^@@ -(\d+)[^\+]+\+(\d+)[^@]+@@(.*)$`) var hunkHeaderRegexp = regexp.MustCompile(`(?m)^@@ -(\d+)[^\+]+\+(\d+)[^@]+@@(.*)$`)
var patchHeaderRegexp = regexp.MustCompile(`(?ms)(^diff.*?)^@@`) var patchHeaderRegexp = regexp.MustCompile(`(?ms)(^diff.*?)^@@`)
type PatchHunk struct {
FirstLineIdx int
oldStart int
newStart int
heading string
bodyLines []string
}
func (hunk *PatchHunk) LastLineIdx() int {
return hunk.FirstLineIdx + len(hunk.bodyLines)
}
func newHunk(lines []string, firstLineIdx int) *PatchHunk {
header := lines[0]
bodyLines := lines[1:]
oldStart, newStart, heading := headerInfo(header)
return &PatchHunk{
oldStart: oldStart,
newStart: newStart,
heading: heading,
FirstLineIdx: firstLineIdx,
bodyLines: bodyLines,
}
}
func headerInfo(header string) (int, int, string) {
match := hunkHeaderRegexp.FindStringSubmatch(header)
oldStart := mustConvertToInt(match[1])
newStart := mustConvertToInt(match[2])
heading := match[3]
return oldStart, newStart, heading
}
func (hunk *PatchHunk) updatedLines(lineIndices []int, reverse bool) []string {
skippedNewlineMessageIndex := -1
newLines := []string{}
lineIdx := hunk.FirstLineIdx
for _, line := range hunk.bodyLines {
lineIdx++ // incrementing at the start to skip the header line
if line == "" {
break
}
isLineSelected := utils.IncludesInt(lineIndices, lineIdx)
firstChar, content := line[:1], line[1:]
transformedFirstChar := transformedFirstChar(firstChar, reverse, isLineSelected)
if isLineSelected || (transformedFirstChar == "\\" && skippedNewlineMessageIndex != lineIdx) || transformedFirstChar == " " {
newLines = append(newLines, transformedFirstChar+content)
continue
}
if transformedFirstChar == "+" {
// we don't want to include the 'newline at end of file' line if it involves an addition we're not including
skippedNewlineMessageIndex = lineIdx + 1
}
}
return newLines
}
func transformedFirstChar(firstChar string, reverse bool, isLineSelected bool) string {
if reverse {
if !isLineSelected && firstChar == "+" {
return " "
} else if firstChar == "-" {
return "+"
} else if firstChar == "+" {
return "-"
} else {
return firstChar
}
}
if !isLineSelected && firstChar == "-" {
return " "
}
return firstChar
}
func (hunk *PatchHunk) formatHeader(oldStart int, oldLength int, newStart int, newLength int, heading string) string {
return fmt.Sprintf("@@ -%d,%d +%d,%d @@%s\n", oldStart, oldLength, newStart, newLength, heading)
}
func (hunk *PatchHunk) formatWithChanges(lineIndices []int, reverse bool, startOffset int) (int, string) {
bodyLines := hunk.updatedLines(lineIndices, reverse)
startOffset, header, ok := hunk.updatedHeader(bodyLines, startOffset, reverse)
if !ok {
return startOffset, ""
}
return startOffset, header + strings.Join(bodyLines, "")
}
func (hunk *PatchHunk) updatedHeader(newBodyLines []string, startOffset int, reverse bool) (int, string, bool) {
changeCount := nLinesWithPrefix(newBodyLines, []string{"+", "-"})
oldLength := nLinesWithPrefix(newBodyLines, []string{" ", "-"})
newLength := nLinesWithPrefix(newBodyLines, []string{"+", " "})
if changeCount == 0 {
// if nothing has changed we just return nothing
return startOffset, "", false
}
var oldStart int
if reverse {
oldStart = hunk.newStart
} else {
oldStart = hunk.oldStart
}
var newStartOffset int
// if the hunk went from zero to positive length, we need to increment the starting point by one
// if the hunk went from positive to zero length, we need to decrement the starting point by one
if oldLength == 0 {
newStartOffset = 1
} else if newLength == 0 {
newStartOffset = -1
} else {
newStartOffset = 0
}
newStart := oldStart + startOffset + newStartOffset
newStartOffset = startOffset + newLength - oldLength
formattedHeader := hunk.formatHeader(oldStart, oldLength, newStart, newLength, hunk.heading)
return newStartOffset, formattedHeader, true
}
func mustConvertToInt(s string) int {
i, err := strconv.Atoi(s)
if err != nil {
panic(err)
}
return i
}
func GetHeaderFromDiff(diff string) string { func GetHeaderFromDiff(diff string) string {
match := patchHeaderRegexp.FindStringSubmatch(diff) match := patchHeaderRegexp.FindStringSubmatch(diff)
if len(match) <= 1 { if len(match) <= 1 {

View File

@ -1,4 +1,4 @@
package commands package patch
import ( import (
"fmt" "fmt"

View File

@ -1,4 +1,4 @@
package commands package patch
import ( import (
"regexp" "regexp"

View File

@ -2,11 +2,13 @@ package commands
import ( import (
"fmt" "fmt"
"github.com/go-errors/errors" "github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/commands/patch"
) )
// DeletePatchesFromCommit applies a patch in reverse for a commit // DeletePatchesFromCommit applies a patch in reverse for a commit
func (c *GitCommand) DeletePatchesFromCommit(commits []*Commit, commitIndex int, p *PatchManager) error { func (c *GitCommand) DeletePatchesFromCommit(commits []*Commit, commitIndex int, p *patch.PatchManager) error {
if err := c.BeginInteractiveRebaseForCommit(commits, commitIndex); err != nil { if err := c.BeginInteractiveRebaseForCommit(commits, commitIndex); err != nil {
return err return err
} }
@ -33,7 +35,7 @@ func (c *GitCommand) DeletePatchesFromCommit(commits []*Commit, commitIndex int,
return c.GenericMerge("rebase", "continue") return c.GenericMerge("rebase", "continue")
} }
func (c *GitCommand) MovePatchToSelectedCommit(commits []*Commit, sourceCommitIdx int, destinationCommitIdx int, p *PatchManager) error { func (c *GitCommand) MovePatchToSelectedCommit(commits []*Commit, sourceCommitIdx int, destinationCommitIdx int, p *patch.PatchManager) error {
if sourceCommitIdx < destinationCommitIdx { if sourceCommitIdx < destinationCommitIdx {
if err := c.BeginInteractiveRebaseForCommit(commits, destinationCommitIdx); err != nil { if err := c.BeginInteractiveRebaseForCommit(commits, destinationCommitIdx); err != nil {
return err return err
@ -134,7 +136,7 @@ func (c *GitCommand) MovePatchToSelectedCommit(commits []*Commit, sourceCommitId
return c.GenericMerge("rebase", "continue") return c.GenericMerge("rebase", "continue")
} }
func (c *GitCommand) PullPatchIntoIndex(commits []*Commit, commitIdx int, p *PatchManager, stash bool) error { func (c *GitCommand) PullPatchIntoIndex(commits []*Commit, commitIdx int, p *patch.PatchManager, stash bool) error {
if stash { if stash {
if err := c.StashSave(c.Tr.SLocalize("StashPrefix") + commits[commitIdx].Sha); err != nil { if err := c.StashSave(c.Tr.SLocalize("StashPrefix") + commits[commitIdx].Sha); err != nil {
return err return err
@ -187,7 +189,7 @@ func (c *GitCommand) PullPatchIntoIndex(commits []*Commit, commitIdx int, p *Pat
return c.GenericMerge("rebase", "continue") return c.GenericMerge("rebase", "continue")
} }
func (c *GitCommand) PullPatchIntoNewCommit(commits []*Commit, commitIdx int, p *PatchManager) error { func (c *GitCommand) PullPatchIntoNewCommit(commits []*Commit, commitIdx int, p *patch.PatchManager) error {
if err := c.BeginInteractiveRebaseForCommit(commits, commitIdx); err != nil { if err := c.BeginInteractiveRebaseForCommit(commits, commitIdx); err != nil {
return err return err
} }

View File

@ -22,6 +22,7 @@ import (
"github.com/golang-collections/collections/stack" "github.com/golang-collections/collections/stack"
"github.com/jesseduffield/gocui" "github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands" "github.com/jesseduffield/lazygit/pkg/commands"
"github.com/jesseduffield/lazygit/pkg/commands/patch"
"github.com/jesseduffield/lazygit/pkg/config" "github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/i18n" "github.com/jesseduffield/lazygit/pkg/i18n"
"github.com/jesseduffield/lazygit/pkg/tasks" "github.com/jesseduffield/lazygit/pkg/tasks"
@ -102,7 +103,7 @@ type lineByLinePanelState struct {
FirstLineIdx int FirstLineIdx int
LastLineIdx int LastLineIdx int
Diff string Diff string
PatchParser *commands.PatchParser PatchParser *patch.PatchParser
SelectMode int // one of LINE, HUNK, or RANGE SelectMode int // one of LINE, HUNK, or RANGE
SecondaryFocused bool // this is for if we show the left or right panel SecondaryFocused bool // this is for if we show the left or right panel
} }

View File

@ -5,7 +5,7 @@ import (
"github.com/go-errors/errors" "github.com/go-errors/errors"
"github.com/jesseduffield/gocui" "github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands" "github.com/jesseduffield/lazygit/pkg/commands/patch"
) )
// Currently there are two 'pseudo-panels' that make use of this 'pseudo-panel'. // Currently there are two 'pseudo-panels' that make use of this 'pseudo-panel'.
@ -27,7 +27,7 @@ const (
func (gui *Gui) refreshLineByLinePanel(diff string, secondaryDiff string, secondaryFocused bool, selectedLineIdx int) (bool, error) { func (gui *Gui) refreshLineByLinePanel(diff string, secondaryDiff string, secondaryFocused bool, selectedLineIdx int) (bool, error) {
state := gui.State.Panels.LineByLine state := gui.State.Panels.LineByLine
patchParser, err := commands.NewPatchParser(gui.Log, diff) patchParser, err := patch.NewPatchParser(gui.Log, diff)
if err != nil { if err != nil {
return false, nil return false, nil
} }
@ -85,7 +85,7 @@ func (gui *Gui) refreshLineByLinePanel(diff string, secondaryDiff string, second
secondaryView.Highlight = true secondaryView.Highlight = true
secondaryView.Wrap = false secondaryView.Wrap = false
secondaryPatchParser, err := commands.NewPatchParser(gui.Log, secondaryDiff) secondaryPatchParser, err := patch.NewPatchParser(gui.Log, secondaryDiff)
if err != nil { if err != nil {
return false, nil return false, nil
} }
@ -120,7 +120,7 @@ func (gui *Gui) handleSelectNextHunk(g *gocui.Gui, v *gocui.View) error {
return gui.selectNewHunk(newHunk) return gui.selectNewHunk(newHunk)
} }
func (gui *Gui) selectNewHunk(newHunk *commands.PatchHunk) error { func (gui *Gui) selectNewHunk(newHunk *patch.PatchHunk) error {
state := gui.State.Panels.LineByLine state := gui.State.Panels.LineByLine
state.SelectedLineIdx = state.PatchParser.GetNextStageableLineIndex(newHunk.FirstLineIdx) state.SelectedLineIdx = state.PatchParser.GetNextStageableLineIndex(newHunk.FirstLineIdx)
if state.SelectMode == HUNK { if state.SelectMode == HUNK {

View File

@ -3,6 +3,7 @@ package presentation
import ( import (
"github.com/fatih/color" "github.com/fatih/color"
"github.com/jesseduffield/lazygit/pkg/commands" "github.com/jesseduffield/lazygit/pkg/commands"
"github.com/jesseduffield/lazygit/pkg/commands/patch"
"github.com/jesseduffield/lazygit/pkg/theme" "github.com/jesseduffield/lazygit/pkg/theme"
) )
@ -26,11 +27,11 @@ func getCommitFileDisplayStrings(f *commands.CommitFile, diffed bool) []string {
var colour *color.Color var colour *color.Color
switch f.Status { switch f.Status {
case commands.UNSELECTED: case patch.UNSELECTED:
colour = defaultColor colour = defaultColor
case commands.WHOLE: case patch.WHOLE:
colour = green colour = green
case commands.PART: case patch.PART:
colour = yellow colour = yellow
} }
if diffed { if diffed {

View File

@ -4,7 +4,7 @@ import (
"strings" "strings"
"github.com/jesseduffield/gocui" "github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands" "github.com/jesseduffield/lazygit/pkg/commands/patch"
) )
func (gui *Gui) refreshStagingPanel(forceSecondaryFocused bool, selectedLineIdx int) error { func (gui *Gui) refreshStagingPanel(forceSecondaryFocused bool, selectedLineIdx int) error {
@ -130,7 +130,7 @@ func (gui *Gui) applySelection(reverse bool) error {
return err return err
} }
patch := commands.ModifiedPatchForRange(gui.Log, file.Name, state.Diff, state.FirstLineIdx, state.LastLineIdx, reverse, false) patch := patch.ModifiedPatchForRange(gui.Log, file.Name, state.Diff, state.FirstLineIdx, state.LastLineIdx, reverse, false)
if patch == "" { if patch == "" {
return nil return nil

View File

@ -7,6 +7,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
"strconv"
"strings" "strings"
"time" "time"
@ -334,3 +335,11 @@ func StringArraysOverlap(strArrA []string, strArrB []string) bool {
return false return false
} }
func MustConvertToInt(s string) int {
i, err := strconv.Atoi(s)
if err != nil {
panic(err)
}
return i
}