-
Notifications
You must be signed in to change notification settings - Fork 127
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
2 changed files
with
334 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
package git | ||
|
||
import ( | ||
"bytes" | ||
"io" | ||
"regexp" | ||
"strconv" | ||
"strings" | ||
) | ||
|
||
// Stash represents a stash in the repository. | ||
type Stash struct { | ||
// Index is the index of the stash. | ||
Index int | ||
|
||
// Message is the message of the stash. | ||
Message string | ||
|
||
// Files is the list of files in the stash. | ||
Files []string | ||
} | ||
|
||
// StashListOptions describes the options for the StashList function. | ||
type StashListOptions struct { | ||
// CommandOptions describes the options for the command. | ||
CommandOptions | ||
} | ||
|
||
var stashLineRegexp = regexp.MustCompile(`^stash@\{(\d+)\}: (.*)$`) | ||
|
||
// StashList returns a list of stashes in the repository. | ||
// This must be run in a work tree. | ||
func (r *Repository) StashList(opts ...StashListOptions) ([]*Stash, error) { | ||
var opt StashListOptions | ||
if len(opts) > 0 { | ||
opt = opts[0] | ||
} | ||
|
||
stash := make([]*Stash, 0) | ||
cmd := NewCommand("stash", "list", "--name-only").AddOptions(opt.CommandOptions) | ||
stdout, stderr := new(bytes.Buffer), new(bytes.Buffer) | ||
if err := cmd.RunInDirPipeline(stdout, stderr, r.path); err != nil { | ||
return nil, concatenateError(err, stderr.String()) | ||
} | ||
|
||
var entry *Stash | ||
lines := strings.Split(stdout.String(), "\n") | ||
for i := 0; i < len(lines); i++ { | ||
line := strings.TrimSpace(lines[i]) | ||
// Init entry | ||
if match := stashLineRegexp.FindStringSubmatch(line); len(match) == 3 { | ||
if entry != nil { | ||
stash = append(stash, entry) | ||
} | ||
|
||
idx, err := strconv.Atoi(match[1]) | ||
if err != nil { | ||
idx = -1 | ||
} | ||
entry = &Stash{ | ||
Index: idx, | ||
Message: match[2], | ||
Files: make([]string, 0), | ||
} | ||
} else if entry != nil && line != "" { | ||
entry.Files = append(entry.Files, line) | ||
} else { | ||
continue | ||
} | ||
} | ||
|
||
if entry != nil { | ||
stash = append(stash, entry) | ||
} | ||
|
||
return stash, nil | ||
} | ||
|
||
// StashDiff returns a parsed diff object for the given stash index. | ||
// This must be run in a work tree. | ||
func (r *Repository) StashDiff(index int, maxFiles, maxFileLines, maxLineChars int, opts ...DiffOptions) (*Diff, error) { | ||
var opt DiffOptions | ||
if len(opts) > 0 { | ||
opt = opts[0] | ||
} | ||
|
||
cmd := NewCommand("stash", "show", "-p", "--full-index", "-M", strconv.Itoa(index)).AddOptions(opt.CommandOptions) | ||
stdout, w := io.Pipe() | ||
done := make(chan SteamParseDiffResult) | ||
go StreamParseDiff(stdout, done, maxFiles, maxFileLines, maxLineChars) | ||
|
||
stderr := new(bytes.Buffer) | ||
err := cmd.RunInDirPipelineWithTimeout(opt.Timeout, w, stderr, r.path) | ||
_ = w.Close() // Close writer to exit parsing goroutine | ||
if err != nil { | ||
return nil, concatenateError(err, stderr.String()) | ||
} | ||
|
||
result := <-done | ||
return result.Diff, result.Err | ||
} | ||
|
||
// StashPushOptions describes the options for the StashPush function. | ||
type StashPushOptions struct { | ||
// CommandOptions describes the options for the command. | ||
CommandOptions | ||
} | ||
|
||
// StashPush pushes the current worktree to the stash. | ||
// This must be run in a work tree. | ||
func (r *Repository) StashPush(msg string, opts ...StashPushOptions) error { | ||
var opt StashPushOptions | ||
if len(opts) > 0 { | ||
opt = opts[0] | ||
} | ||
|
||
cmd := NewCommand("stash", "push") | ||
if msg != "" { | ||
cmd.AddArgs("-m", msg) | ||
} | ||
cmd.AddOptions(opt.CommandOptions) | ||
|
||
_, err := cmd.RunInDir(r.path) | ||
return err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,209 @@ | ||
package git | ||
|
||
import ( | ||
"os" | ||
"path/filepath" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestStashWorktreeError(t *testing.T) { | ||
_, err := testrepo.StashList() | ||
if err == nil { | ||
t.Errorf("StashList() error = %v, wantErr %v", err, true) | ||
return | ||
} | ||
} | ||
|
||
func TestStash(t *testing.T) { | ||
tmp := t.TempDir() | ||
path, err := filepath.Abs(repoPath) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
if err := Clone("file://"+path, tmp); err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
repo, err := Open(tmp) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
if err := os.WriteFile(tmp+"/resources/newfile", []byte("hello, world!"), 0o644); err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
f, err := os.OpenFile(tmp+"/README.txt", os.O_APPEND|os.O_WRONLY, 0o644) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
if _, err := f.WriteString("\n\ngit-module"); err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
f.Close() | ||
if err := repo.Add(AddOptions{ | ||
All: true, | ||
}); err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
if err := repo.StashPush(""); err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
f, err = os.OpenFile(tmp+"/README.txt", os.O_APPEND|os.O_WRONLY, 0o644) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
if _, err := f.WriteString("\n\nstash 1"); err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
f.Close() | ||
if err := repo.Add(AddOptions{ | ||
All: true, | ||
}); err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
if err := repo.StashPush("custom message"); err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
want := []*Stash{ | ||
{ | ||
Index: 0, | ||
Message: "On master: custom message", | ||
Files: []string{"README.txt"}, | ||
}, | ||
{ | ||
Index: 1, | ||
Message: "WIP on master: cfc3b29 Add files with same SHA", | ||
Files: []string{"README.txt", "resources/newfile"}, | ||
}, | ||
} | ||
|
||
stash, err := repo.StashList(StashListOptions{ | ||
CommandOptions: CommandOptions{ | ||
Envs: []string{"GIT_CONFIG_GLOBAL=/dev/null"}, | ||
}, | ||
}) | ||
require.NoError(t, err) | ||
require.Equalf(t, want, stash, "StashList() got = %v, want %v", stash, want) | ||
|
||
wantDiff := &Diff{ | ||
totalAdditions: 4, | ||
totalDeletions: 0, | ||
isIncomplete: false, | ||
Files: []*DiffFile{ | ||
{ | ||
Name: "README.txt", | ||
Type: DiffFileChange, | ||
Index: "72e29aca01368bc0aca5d599c31fa8705b11787d", | ||
OldIndex: "adfd6da3c0a3fb038393144becbf37f14f780087", | ||
Sections: []*DiffSection{ | ||
{ | ||
Lines: []*DiffLine{ | ||
{ | ||
Type: DiffLineSection, | ||
Content: `@@ -13,3 +13,6 @@ As a quick reminder, this came from one of three locations in either SSH, Git, o`, | ||
}, | ||
{ | ||
Type: DiffLinePlain, | ||
Content: " We can, as an example effort, even modify this README and change it as if it were source code for the purposes of the class.", | ||
LeftLine: 13, | ||
RightLine: 13, | ||
}, | ||
{ | ||
Type: DiffLinePlain, | ||
Content: " ", | ||
LeftLine: 14, | ||
RightLine: 14, | ||
}, | ||
{ | ||
Type: DiffLinePlain, | ||
Content: " This demo also includes an image with changes on a branch for examination of image diff on GitHub.", | ||
LeftLine: 15, | ||
RightLine: 15, | ||
}, | ||
{ | ||
Type: DiffLineAdd, | ||
Content: "+", | ||
LeftLine: 0, | ||
RightLine: 16, | ||
}, | ||
{ | ||
Type: DiffLineAdd, | ||
Content: "+", | ||
LeftLine: 0, | ||
RightLine: 17, | ||
}, | ||
{ | ||
Type: DiffLineAdd, | ||
Content: "+git-module", | ||
LeftLine: 0, | ||
RightLine: 18, | ||
}, | ||
}, | ||
numAdditions: 3, | ||
numDeletions: 0, | ||
}, | ||
}, | ||
numAdditions: 3, | ||
numDeletions: 0, | ||
oldName: "README.txt", | ||
mode: 0o100644, | ||
oldMode: 0o100644, | ||
isBinary: false, | ||
isSubmodule: false, | ||
isIncomplete: false, | ||
}, | ||
{ | ||
Name: "resources/newfile", | ||
Type: DiffFileAdd, | ||
Index: "30f51a3fba5274d53522d0f19748456974647b4f", | ||
OldIndex: "0000000000000000000000000000000000000000", | ||
Sections: []*DiffSection{ | ||
{ | ||
Lines: []*DiffLine{ | ||
{ | ||
Type: DiffLineSection, | ||
Content: "@@ -0,0 +1 @@", | ||
}, | ||
{ | ||
Type: DiffLineAdd, | ||
Content: "+hello, world!", | ||
LeftLine: 0, | ||
RightLine: 1, | ||
}, | ||
}, | ||
numAdditions: 1, | ||
numDeletions: 0, | ||
}, | ||
}, | ||
numAdditions: 1, | ||
numDeletions: 0, | ||
oldName: "resources/newfile", | ||
mode: 0o100644, | ||
oldMode: 0o100644, | ||
isBinary: false, | ||
isSubmodule: false, | ||
isIncomplete: false, | ||
}, | ||
}, | ||
} | ||
|
||
diff, err := repo.StashDiff(want[1].Index, 0, 0, 0, DiffOptions{ | ||
CommandOptions: CommandOptions{ | ||
Envs: []string{"GIT_CONFIG_GLOBAL=/dev/null"}, | ||
}, | ||
}) | ||
require.NoError(t, err) | ||
require.Equalf(t, wantDiff, diff, "StashDiff() got = %v, want %v", diff, wantDiff) | ||
} |