Compare commits

...
27 Commits
Author SHA1 Message Date
Jesse Duffield 307d051ec2 smarter checking of git version 2020-09-18 21:02:27 +10:00
Jesse Duffield 3a668011fa better flag description 2020-09-18 08:40:30 +10:00
Jesse Duffield 14c8b80494 show loading state when amending top commit 2020-09-18 07:58:16 +10:00
Lakshay Garg 10dde518bc Fix numbering in Dutch IntroPopupMessage 2020-09-18 07:47:01 +10:00
Lakshay Garg 1e1c90c92e Fix numbering in English IntroPopupMessage 2020-09-18 07:47:01 +10:00
Jesse Duffield 4954791443 fix test 2020-09-18 07:46:12 +10:00
Jesse Duffield c471f4927a fix test 2020-09-02 20:55:53 +10:00
Jesse Duffield 9eba98302e ensure that when a branch name is ambiguous we still show the correct colours 2020-09-02 10:40:50 +00:00
Francisco Miamoto 250fe740b2 use GetBool instead of casting 2020-08-31 09:22:39 +10:00
Francisco Miamoto 70eda031dc implement config option for disabling force pushing 2020-08-31 09:22:39 +10:00
Francisco Miamoto 86f296a898 add config for disabling force pushing 2020-08-31 09:22:39 +10:00
Jesse Duffield 71ff18318d fast UI update when moving commits in rebase mode 2020-08-29 00:19:31 +00:00
Jesse Duffield 46cce28758 restore donate link 2020-08-28 09:52:56 +10:00
Jesse Duffield 5611d9a3ef gracefully fail due to git version less than 2.0 2020-08-27 12:21:37 +00:00
Jesse Duffield 40bec49de8 more efficient refreshing of rebase commits 2020-08-27 21:51:07 +10:00
Jesse Duffield f99d5f74d4 drop merge commits when interactive rebasing just like git CLI 2020-08-27 21:51:07 +10:00
Jesse Duffield 30a066aa41 remove redundant test 2020-08-27 19:29:22 +10:00
Jesse Duffield 1dcc3363d0 support branches with no upstream 2020-08-27 17:05:07 +10:00
Jesse Duffield c6948582e6 better way of knowing which commits are unpushed 2020-08-26 22:45:55 +00:00
Jesse Duffield 196c83d058 fix bug where cancelling search in menu caused issue 2020-08-26 09:32:57 +00:00
Jesse Duffield 806bee9646 try again 2020-08-26 08:57:32 +10:00
Jesse Duffield 45a0378c01 do not create error panel for sentinel errors 2020-08-25 22:21:15 +00:00
Jesse Duffield afd669194a use clipboard package to handle clipboard stuff 2020-08-26 07:53:43 +10:00
Jesse Duffield 1494a3863d Remove tab keybinding for cycling tab
This keybinding has been more pain than it's worth. Having a tab keybinding
to cycle tabs implies that you can shift+tab and when you shift+tab the
application exits because termbox, our dependency, doesn't know how to
interpret the escape sequence (so it takes it for an actual ESC key which
will exit lazygit at the top level).

If people get mad at me they can set nextBlock-alt to <tab> and they'll have
the functionality back :)
2020-08-25 10:48:13 +00:00
Jesse Duffield f5c55f066b use new branch logic when 'checking out' remote branch 2020-08-25 09:25:17 +00:00
Dawid Dziurla bd8f198beb workflows: run CI on master or pull request only 2020-08-25 09:18:29 +02:00
Dawid Dziurla f05adb4f99 workflows: match every branch
Previously, branches with `/` in the name wasn't being matched
2020-08-25 09:16:13 +02:00
38 changed files with 765 additions and 164 deletions
+2 -2
View File
@@ -3,8 +3,8 @@ name: Continuous Integration
on:
push:
branches:
- '*'
pull_request: []
- master
pull_request:
jobs:
ci:
+1
View File
@@ -49,6 +49,7 @@ Default path for the config file:
autoFetch: true
branchLogCmd: "git log --graph --color=always --abbrev-commit --decorate --date=relative --pretty=medium {{branchName}} --"
overrideGpg: false # prevents lazygit from spawning a separate process when using GPG
disableForcePushing: false
update:
method: prompt # can be: prompt | background | never
days: 14 # how often an update is checked for
+1
View File
@@ -3,6 +3,7 @@ module github.com/jesseduffield/lazygit
go 1.14
require (
github.com/atotto/clipboard v0.1.2
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21
github.com/creack/pty v1.1.11
github.com/fatih/color v1.7.0
+2
View File
@@ -12,6 +12,8 @@ github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYU
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/atotto/clipboard v0.1.2 h1:YZCtFu5Ie8qX2VmVTBnrqLSiU9XOWwqNRmdT3gIQzbY=
github.com/atotto/clipboard v0.1.2/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
+1 -1
View File
@@ -39,7 +39,7 @@ func main() {
flaggy.Bool(&debuggingFlag, "d", "debug", "Run in debug mode with logging")
configFlag := false
flaggy.Bool(&configFlag, "c", "config", "Print the current default config")
flaggy.Bool(&configFlag, "c", "config", "Print the default config")
flaggy.Parse()
+51
View File
@@ -2,11 +2,14 @@ package app
import (
"bufio"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"github.com/jesseduffield/lazygit/pkg/commands"
@@ -129,7 +132,47 @@ func NewApp(config config.AppConfigurer, filterPath string) (*App, error) {
return app, nil
}
func (app *App) validateGitVersion() error {
output, err := app.OSCommand.RunCommandWithOutput("git --version")
// if we get an error anywhere here we'll show the same status
minVersionError := errors.New(app.Tr.SLocalize("minGitVersionError"))
if err != nil {
return minVersionError
}
if isGitVersionValid(output) {
return nil
}
return minVersionError
}
func isGitVersionValid(versionStr string) bool {
// output should be something like: 'git version 2.23.0 (blah)'
re := regexp.MustCompile(`[^\d]+([\d\.]+)`)
matches := re.FindStringSubmatch(versionStr)
if len(matches) == 0 {
return false
}
gitVersion := matches[1]
majorVersion, err := strconv.Atoi(gitVersion[0:1])
if err != nil {
return false
}
if majorVersion < 2 {
return false
}
return true
}
func (app *App) setupRepo() (bool, error) {
if err := app.validateGitVersion(); err != nil {
return false, err
}
// if we are not in a git repo, we ask if we want to `git init`
if err := app.OSCommand.RunCommand("git status"); err != nil {
cwd, err := os.Getwd()
@@ -216,6 +259,14 @@ func (app *App) Close() error {
func (app *App) KnownError(err error) (string, bool) {
errorMessage := err.Error()
knownErrorMessages := []string{app.Tr.SLocalize("minGitVersionError")}
for _, message := range knownErrorMessages {
if errorMessage == message {
return message, true
}
}
mappings := []errorMapping{
{
originalError: "fatal: not a git repository",
+44
View File
@@ -0,0 +1,44 @@
package app
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestIsGitVersionValid(t *testing.T) {
type scenario struct {
versionStr string
expectedResult bool
}
scenarios := []scenario{
{
"",
false,
},
{
"git version 1.9.0",
false,
},
{
"git version 1.9.0 (Apple Git-128)",
false,
},
{
"git version 2.4.0",
true,
},
{
"git version 2.24.3 (Apple Git-128)",
true,
},
}
for _, s := range scenarios {
t.Run(s.versionStr, func(t *testing.T) {
result := isGitVersionValid(s.versionStr)
assert.Equal(t, result, s.expectedResult)
})
}
}
+3
View File
@@ -12,6 +12,9 @@ type Commit struct {
ExtraInfo string // something like 'HEAD -> master, tag: v0.15.2'
Author string
UnixTimestamp int64
// IsMerge tells us whether we're dealing with a merge commit i.e. a commit with two parents
IsMerge bool
}
func (c *Commit) ShortSha() string {
+103 -47
View File
@@ -12,7 +12,6 @@ import (
"github.com/fatih/color"
"github.com/jesseduffield/lazygit/pkg/i18n"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/sirupsen/logrus"
)
@@ -29,21 +28,19 @@ const SEPARATION_CHAR = "|"
// CommitListBuilder returns a list of Branch objects for the current repo
type CommitListBuilder struct {
Log *logrus.Entry
GitCommand *GitCommand
OSCommand *OSCommand
Tr *i18n.Localizer
CherryPickedCommits []*Commit
Log *logrus.Entry
GitCommand *GitCommand
OSCommand *OSCommand
Tr *i18n.Localizer
}
// NewCommitListBuilder builds a new commit list builder
func NewCommitListBuilder(log *logrus.Entry, gitCommand *GitCommand, osCommand *OSCommand, tr *i18n.Localizer, cherryPickedCommits []*Commit) *CommitListBuilder {
func NewCommitListBuilder(log *logrus.Entry, gitCommand *GitCommand, osCommand *OSCommand, tr *i18n.Localizer) *CommitListBuilder {
return &CommitListBuilder{
Log: log,
GitCommand: gitCommand,
OSCommand: osCommand,
Tr: tr,
CherryPickedCommits: cherryPickedCommits,
Log: log,
GitCommand: gitCommand,
OSCommand: osCommand,
Tr: tr,
}
}
@@ -58,7 +55,9 @@ func (c *CommitListBuilder) extractCommitFromLine(line string) *Commit {
unixTimestamp := split[1]
author := split[2]
extraInfo := strings.TrimSpace(split[3])
message := strings.Join(split[4:], SEPARATION_CHAR)
parentHashes := split[4]
message := strings.Join(split[5:], SEPARATION_CHAR)
tags := []string{}
if extraInfo != "" {
@@ -71,6 +70,10 @@ func (c *CommitListBuilder) extractCommitFromLine(line string) *Commit {
unitTimestampInt, _ := strconv.Atoi(unixTimestamp)
// Any commit with multiple parents is a merge commit.
// If there's a space then it means there must be more than one parent hash
isMerge := strings.Contains(parentHashes, " ")
return &Commit{
Sha: sha,
Name: message,
@@ -78,6 +81,7 @@ func (c *CommitListBuilder) extractCommitFromLine(line string) *Commit {
ExtraInfo: extraInfo,
UnixTimestamp: int64(unitTimestampInt),
Author: author,
IsMerge: isMerge,
}
}
@@ -88,38 +92,71 @@ type GetCommitsOptions struct {
RefName string // e.g. "HEAD" or "my_branch"
}
func (c *CommitListBuilder) MergeRebasingCommits(commits []*Commit) ([]*Commit, error) {
// chances are we have as many commits as last time so we'll set the capacity to be the old length
result := make([]*Commit, 0, len(commits))
for i, commit := range commits {
if commit.Status != "rebasing" { // removing the existing rebase commits so we can add the refreshed ones
result = append(result, commits[i:]...)
break
}
}
rebaseMode, err := c.GitCommand.RebaseMode()
if err != nil {
return nil, err
}
if rebaseMode == "" {
// not in rebase mode so return original commits
return result, nil
}
rebasingCommits, err := c.getRebasingCommits(rebaseMode)
if err != nil {
return nil, err
}
if len(rebasingCommits) > 0 {
result = append(rebasingCommits, result...)
}
return result, nil
}
// GetCommits obtains the commits of the current branch
func (c *CommitListBuilder) GetCommits(opts GetCommitsOptions) ([]*Commit, error) {
commits := []*Commit{}
var rebasingCommits []*Commit
rebaseMode := ""
rebaseMode, err := c.GitCommand.RebaseMode()
if err != nil {
return nil, err
}
if opts.IncludeRebaseCommits {
if opts.IncludeRebaseCommits && opts.FilterPath == "" {
var err error
rebaseMode, err = c.GitCommand.RebaseMode()
rebasingCommits, err = c.MergeRebasingCommits(commits)
if err != nil {
return nil, err
}
if rebaseMode != "" && opts.FilterPath == "" {
// here we want to also prepend the commits that we're in the process of rebasing
rebasingCommits, err = c.getRebasingCommits(rebaseMode)
if err != nil {
return nil, err
}
if len(rebasingCommits) > 0 {
commits = append(commits, rebasingCommits...)
}
}
commits = append(commits, rebasingCommits...)
}
passedFirstPushedCommit := false
firstPushedCommit, err := c.getFirstPushedCommit(opts.RefName)
if err != nil {
// must have no upstream branch so we'll consider everything as pushed
passedFirstPushedCommit = true
}
unpushedCommits := c.getUnpushedCommits(opts.RefName)
cmd := c.getLogCmd(opts)
err := RunLineOutputCmd(cmd, func(line string) (bool, error) {
err = RunLineOutputCmd(cmd, func(line string) (bool, error) {
if strings.Split(line, " ")[0] != "gpg:" {
commit := c.extractCommitFromLine(line)
_, unpushed := unpushedCommits[commit.ShortSha()]
commit.Status = map[bool]string{true: "unpushed", false: "pushed"}[unpushed]
if commit.Sha == firstPushedCommit {
passedFirstPushedCommit = true
}
commit.Status = map[bool]string{true: "unpushed", false: "pushed"}[!passedFirstPushedCommit]
commits = append(commits, commit)
}
return false, nil
@@ -135,7 +172,7 @@ func (c *CommitListBuilder) GetCommits(opts GetCommitsOptions) ([]*Commit, error
currentCommit.Name = fmt.Sprintf("%s %s", youAreHere, currentCommit.Name)
}
commits, err = c.setCommitMergedStatuses(commits)
commits, err = c.setCommitMergedStatuses(opts.RefName, commits)
if err != nil {
return nil, err
}
@@ -253,8 +290,8 @@ func (c *CommitListBuilder) commitFromPatch(content string) (*Commit, error) {
}, nil
}
func (c *CommitListBuilder) setCommitMergedStatuses(commits []*Commit) ([]*Commit, error) {
ancestor, err := c.getMergeBase()
func (c *CommitListBuilder) setCommitMergedStatuses(refName string, commits []*Commit) ([]*Commit, error) {
ancestor, err := c.getMergeBase(refName)
if err != nil {
return nil, err
}
@@ -276,7 +313,7 @@ func (c *CommitListBuilder) setCommitMergedStatuses(commits []*Commit) ([]*Commi
return commits, nil
}
func (c *CommitListBuilder) getMergeBase() (string, error) {
func (c *CommitListBuilder) getMergeBase(refName string) (string, error) {
currentBranch, _, err := c.GitCommand.CurrentBranchName()
if err != nil {
return "", err
@@ -288,23 +325,29 @@ func (c *CommitListBuilder) getMergeBase() (string, error) {
}
// swallowing error because it's not a big deal; probably because there are no commits yet
output, _ := c.OSCommand.RunCommandWithOutput("git merge-base HEAD %s", baseBranch)
return output, nil
output, _ := c.OSCommand.RunCommandWithOutput("git merge-base %s %s", refName, baseBranch)
return ignoringWarnings(output), nil
}
// getUnpushedCommits Returns the sha's of the commits that have not yet been pushed
// to the remote branch of the current branch, a map is returned to ease look up
func (c *CommitListBuilder) getUnpushedCommits(refName string) map[string]bool {
pushables := map[string]bool{}
o, err := c.OSCommand.RunCommandWithOutput("git rev-list %s@{u}..%s --abbrev-commit --abbrev=8", refName, refName)
func ignoringWarnings(commandOutput string) string {
trimmedOutput := strings.TrimSpace(commandOutput)
split := strings.Split(trimmedOutput, "\n")
// need to get last line in case the first line is a warning about how the error is ambiguous.
// At some point we should find a way to make it unambiguous
lastLine := split[len(split)-1]
return lastLine
}
// getFirstPushedCommit returns the first commit SHA which has been pushed to the ref's upstream.
// all commits above this are deemed unpushed and marked as such.
func (c *CommitListBuilder) getFirstPushedCommit(refName string) (string, error) {
output, err := c.OSCommand.RunCommandWithOutput("git merge-base %s %s@{u}", refName, refName)
if err != nil {
return pushables
}
for _, p := range utils.SplitLines(o) {
pushables[p] = true
return "", err
}
return pushables
return ignoringWarnings(output), nil
}
// getLog gets the git log.
@@ -319,5 +362,18 @@ func (c *CommitListBuilder) getLogCmd(opts GetCommitsOptions) *exec.Cmd {
filterFlag = fmt.Sprintf(" --follow -- %s", c.OSCommand.Quote(opts.FilterPath))
}
return c.OSCommand.ExecutableFromString(fmt.Sprintf("git log %s --oneline --pretty=format:\"%%H%s%%at%s%%aN%s%%d%s%%s\" %s --abbrev=%d --date=unix %s", opts.RefName, SEPARATION_CHAR, SEPARATION_CHAR, SEPARATION_CHAR, SEPARATION_CHAR, limitFlag, 20, filterFlag))
return c.OSCommand.ExecutableFromString(
fmt.Sprintf(
"git log %s --oneline --pretty=format:\"%%H%s%%at%s%%aN%s%%d%s%%p%s%%s\" %s --abbrev=%d --date=unix %s",
opts.RefName,
SEPARATION_CHAR,
SEPARATION_CHAR,
SEPARATION_CHAR,
SEPARATION_CHAR,
SEPARATION_CHAR,
limitFlag,
20,
filterFlag,
),
)
}
+7 -47
View File
@@ -13,50 +13,10 @@ func NewDummyCommitListBuilder() *CommitListBuilder {
osCommand := NewDummyOSCommand()
return &CommitListBuilder{
Log: NewDummyLog(),
GitCommand: NewDummyGitCommandWithOSCommand(osCommand),
OSCommand: osCommand,
Tr: i18n.NewLocalizer(NewDummyLog()),
CherryPickedCommits: []*Commit{},
}
}
// TestCommitListBuilderGetUnpushedCommits is a function.
func TestCommitListBuilderGetUnpushedCommits(t *testing.T) {
type scenario struct {
testName string
command func(string, ...string) *exec.Cmd
test func(map[string]bool)
}
scenarios := []scenario{
{
"Can't retrieve pushable commits",
func(string, ...string) *exec.Cmd {
return exec.Command("test")
},
func(pushables map[string]bool) {
assert.EqualValues(t, map[string]bool{}, pushables)
},
},
{
"Retrieve pushable commits",
func(cmd string, args ...string) *exec.Cmd {
return exec.Command("echo", "8a2bb0e\n78976bc")
},
func(pushables map[string]bool) {
assert.Len(t, pushables, 2)
assert.EqualValues(t, map[string]bool{"8a2bb0e": true, "78976bc": true}, pushables)
},
},
}
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
c := NewDummyCommitListBuilder()
c.OSCommand.SetCommand(s.command)
s.test(c.getUnpushedCommits("HEAD"))
})
Log: NewDummyLog(),
GitCommand: NewDummyGitCommandWithOSCommand(osCommand),
OSCommand: osCommand,
Tr: i18n.NewLocalizer(NewDummyLog()),
}
}
@@ -106,7 +66,7 @@ func TestCommitListBuilderGetMergeBase(t *testing.T) {
},
func(output string, err error) {
assert.NoError(t, err)
assert.Equal(t, "blah\n", output)
assert.Equal(t, "blah", output)
},
},
{
@@ -126,7 +86,7 @@ func TestCommitListBuilderGetMergeBase(t *testing.T) {
},
func(output string, err error) {
assert.NoError(t, err)
assert.Equal(t, "blah\n", output)
assert.Equal(t, "blah", output)
},
},
{
@@ -145,7 +105,7 @@ func TestCommitListBuilderGetMergeBase(t *testing.T) {
t.Run(s.testName, func(t *testing.T) {
c := NewDummyCommitListBuilder()
c.OSCommand.SetCommand(s.command)
s.test(c.getMergeBase())
s.test(c.getMergeBase("HEAD"))
})
}
}
+12 -4
View File
@@ -910,7 +910,8 @@ func (c *GitCommand) PrepareInteractiveRebaseCommand(baseSha string, todo string
debug = "TRUE"
}
splitCmd := str.ToArgv(fmt.Sprintf("git rebase --interactive --autostash --keep-empty --rebase-merges %s", baseSha))
cmdStr := fmt.Sprintf("git rebase --interactive --autostash --keep-empty %s", baseSha)
splitCmd := str.ToArgv(cmdStr)
cmd := c.OSCommand.command(splitCmd[0], splitCmd[1:]...)
@@ -962,11 +963,18 @@ func (c *GitCommand) GenerateGenericRebaseTodo(commits []*Commit, actionIndex in
todo := ""
for i, commit := range commits[0:baseIndex] {
a := "pick"
var commitAction string
if i == actionIndex {
a = action
commitAction = action
} else if commit.IsMerge {
// your typical interactive rebase will actually drop merge commits by default. Damn git CLI, you scary!
// doing this means we don't need to worry about rebasing over merges which always causes problems.
// you typically shouldn't be doing rebases that pass over merge commits anyway.
commitAction = "drop"
} else {
commitAction = "pick"
}
todo = a + " " + commit.Sha + " " + commit.Name + "\n" + todo
todo = commitAction + " " + commit.Sha + " " + commit.Name + "\n" + todo
}
return todo, commits[baseIndex].Sha, nil
+3 -3
View File
@@ -1639,7 +1639,7 @@ func TestGitCommandRebaseBranch(t *testing.T) {
"master",
test.CreateMockCommand(t, []*test.CommandSwapper{
{
Expect: "git rebase --interactive --autostash --keep-empty --rebase-merges master",
Expect: "git rebase --interactive --autostash --keep-empty master",
Replace: "echo",
},
}),
@@ -1652,7 +1652,7 @@ func TestGitCommandRebaseBranch(t *testing.T) {
"master",
test.CreateMockCommand(t, []*test.CommandSwapper{
{
Expect: "git rebase --interactive --autostash --keep-empty --rebase-merges master",
Expect: "git rebase --interactive --autostash --keep-empty master",
Replace: "test",
},
}),
@@ -1775,7 +1775,7 @@ func TestGitCommandDiscardOldFileChanges(t *testing.T) {
"test999.txt",
test.CreateMockCommand(t, []*test.CommandSwapper{
{
Expect: "git rebase --interactive --autostash --keep-empty --rebase-merges abcdef",
Expect: "git rebase --interactive --autostash --keep-empty abcdef",
Replace: "echo",
},
{
+2 -8
View File
@@ -14,6 +14,7 @@ import (
"github.com/go-errors/errors"
"github.com/atotto/clipboard"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/mgutz/str"
@@ -475,12 +476,5 @@ func RunLineOutputCmd(cmd *exec.Cmd, onLine func(line string) (bool, error)) err
}
func (c *OSCommand) CopyToClipboard(str string) error {
commandTemplate := c.Config.GetUserConfig().GetString("os.copyToClipboardCommand")
templateValues := map[string]string{
"str": c.Quote(str),
}
command := utils.ResolvePlaceholderString(commandTemplate, templateValues)
return c.RunCommand(command)
return clipboard.WriteAll(str)
}
+1
View File
@@ -276,6 +276,7 @@ git:
autoFetch: true
branchLogCmd: "git log --graph --color=always --abbrev-commit --decorate --date=relative --pretty=medium {{branchName}} --"
overrideGpg: false # prevents lazygit from spawning a separate process when using GPG
disableForcePushing: false
update:
method: prompt # can be: prompt | background | never
days: 14 # how often a update is checked for
+1 -2
View File
@@ -7,6 +7,5 @@ func GetPlatformDefaultConfig() []byte {
return []byte(
`os:
openCommand: 'open {{filename}}'
openLinkCommand: 'open {{link}}'
copyToClipboardCommand: 'bash -c "echo -n {{str}} | pbcopy"'`)
openLinkCommand: 'open {{link}}'`)
}
+1 -2
View File
@@ -5,6 +5,5 @@ func GetPlatformDefaultConfig() []byte {
return []byte(
`os:
openCommand: 'sh -c "xdg-open {{filename}} >/dev/null"'
openLinkCommand: 'sh -c "xdg-open {{link}} >/dev/null"'
copyToClipboardCommand: 'bash -c "echo -n {{str}} | xclip -selection clipboard"'`)
openLinkCommand: 'sh -c "xdg-open {{link}} >/dev/null"'`)
}
+1 -2
View File
@@ -5,6 +5,5 @@ func GetPlatformDefaultConfig() []byte {
return []byte(
`os:
openCommand: 'cmd /c "start "" {{filename}}"'
openLinkCommand: 'cmd /c "start "" {{link}}"'
copyToClipboardCommand: 'cmd \c "echo -n {{str}} > /dev/clipboard"'`)
openLinkCommand: 'cmd /c "start "" {{link}}"'`)
}
+23 -6
View File
@@ -97,7 +97,10 @@ func (gui *Gui) refreshCommits() error {
}
func (gui *Gui) refreshCommitsWithLimit() error {
builder := commands.NewCommitListBuilder(gui.Log, gui.GitCommand, gui.OSCommand, gui.Tr, gui.State.Modes.CherryPicking.CherryPickedCommits)
gui.State.BranchCommitsMutex.Lock()
defer gui.State.BranchCommitsMutex.Unlock()
builder := commands.NewCommitListBuilder(gui.Log, gui.GitCommand, gui.OSCommand, gui.Tr)
commits, err := builder.GetCommits(
commands.GetCommitsOptions{
@@ -115,6 +118,21 @@ func (gui *Gui) refreshCommitsWithLimit() error {
return gui.postRefreshUpdate(gui.Contexts.BranchCommits.Context)
}
func (gui *Gui) refreshRebaseCommits() error {
gui.State.BranchCommitsMutex.Lock()
defer gui.State.BranchCommitsMutex.Unlock()
builder := commands.NewCommitListBuilder(gui.Log, gui.GitCommand, gui.OSCommand, gui.Tr)
updatedCommits, err := builder.MergeRebasingCommits(gui.State.Commits)
if err != nil {
return err
}
gui.State.Commits = updatedCommits
return gui.postRefreshUpdate(gui.Contexts.BranchCommits.Context)
}
// specific functions
func (gui *Gui) handleCommitSquashDown(g *gocui.Gui, v *gocui.View) error {
@@ -256,9 +274,8 @@ func (gui *Gui) handleMidRebaseCommand(action string) (bool, error) {
if err := gui.GitCommand.EditRebaseTodo(gui.State.Panels.Commits.SelectedLineIdx, action); err != nil {
return false, gui.surfaceError(err)
}
// TODO: consider doing this in a way that is less expensive. We don't actually
// need to reload all the commits, just the TODO commits.
return true, gui.refreshSidePanels(refreshOptions{scope: []int{COMMITS}})
return true, gui.refreshRebaseCommits()
}
func (gui *Gui) handleCommitDelete(g *gocui.Gui, v *gocui.View) error {
@@ -301,7 +318,7 @@ func (gui *Gui) handleCommitMoveDown(g *gocui.Gui, v *gocui.View) error {
return gui.surfaceError(err)
}
gui.State.Panels.Commits.SelectedLineIdx++
return gui.refreshSidePanels(refreshOptions{mode: BLOCK_UI, scope: []int{COMMITS, BRANCHES}})
return gui.refreshRebaseCommits()
}
return gui.WithWaitingStatus(gui.Tr.SLocalize("MovingStatus"), func() error {
@@ -328,7 +345,7 @@ func (gui *Gui) handleCommitMoveUp(g *gocui.Gui, v *gocui.View) error {
return gui.surfaceError(err)
}
gui.State.Panels.Commits.SelectedLineIdx--
return gui.refreshSidePanels(refreshOptions{mode: BLOCK_UI, scope: []int{COMMITS, BRANCHES}})
return gui.refreshRebaseCommits()
}
return gui.WithWaitingStatus(gui.Tr.SLocalize("MovingStatus"), func() error {
+6
View File
@@ -244,5 +244,11 @@ func (gui *Gui) createErrorPanel(message string) error {
}
func (gui *Gui) surfaceError(err error) error {
for _, sentinelError := range gui.sentinelErrorsArr() {
if err == sentinelError {
return err
}
}
return gui.createErrorPanel(err.Error())
}
+20 -9
View File
@@ -332,15 +332,17 @@ func (gui *Gui) handleAmendCommitPress() error {
title: strings.Title(gui.Tr.SLocalize("AmendLastCommit")),
prompt: gui.Tr.SLocalize("SureToAmend"),
handleConfirm: func() error {
ok, err := gui.runSyncOrAsyncCommand(gui.GitCommand.AmendHead())
if err != nil {
return err
}
if !ok {
return nil
}
return gui.WithWaitingStatus(gui.Tr.SLocalize("AmendingStatus"), func() error {
ok, err := gui.runSyncOrAsyncCommand(gui.GitCommand.AmendHead())
if err != nil {
return err
}
if !ok {
return nil
}
return gui.refreshSidePanels(refreshOptions{mode: ASYNC})
return gui.refreshSidePanels(refreshOptions{mode: ASYNC})
})
},
})
}
@@ -517,6 +519,11 @@ func (gui *Gui) pushWithForceFlag(v *gocui.View, force bool, upstream string, ar
branchName := gui.getCheckedOutBranch().Name
err := gui.GitCommand.Push(branchName, force, upstream, args, gui.promptUserForCredential)
if err != nil && !force && strings.Contains(err.Error(), "Updates were rejected") {
forcePushDisabled := gui.Config.GetUserConfig().GetBool("git.disableForcePushing")
if forcePushDisabled {
gui.createErrorPanel(gui.Tr.SLocalize("UpdatesRejectedAndForcePushDisabled"))
return
}
gui.ask(askOpts{
title: gui.Tr.SLocalize("ForcePush"),
prompt: gui.Tr.SLocalize("ForcePushPrompt"),
@@ -524,7 +531,6 @@ func (gui *Gui) pushWithForceFlag(v *gocui.View, force bool, upstream string, ar
return gui.pushWithForceFlag(v, true, upstream, args)
},
})
return
}
gui.handleCredentialsPopup(err)
@@ -564,6 +570,11 @@ func (gui *Gui) pushFiles(g *gocui.Gui, v *gocui.View) error {
return gui.pushWithForceFlag(v, false, "", "")
}
forcePushDisabled := gui.Config.GetUserConfig().GetBool("git.disableForcePushing")
if forcePushDisabled {
return gui.createErrorPanel(gui.Tr.SLocalize("ForcePushDisabled"))
}
return gui.ask(askOpts{
title: gui.Tr.SLocalize("ForcePush"),
prompt: gui.Tr.SLocalize("ForcePushPrompt"),
+3 -4
View File
@@ -147,12 +147,11 @@ func (gui *Gui) handleInfoClick(g *gocui.Gui, v *gocui.View) error {
cx, _ := v.Cursor()
width, _ := v.Size()
if width-cx > len(gui.Tr.SLocalize("(reset)")) {
return nil
}
for _, mode := range gui.modeStatuses() {
if mode.isActive() {
if width-cx > len(gui.Tr.SLocalize("(reset)")) {
return nil
}
return mode.reset()
}
}
+10
View File
@@ -72,6 +72,15 @@ func (gui *Gui) GenerateSentinelErrors() {
}
}
func (gui *Gui) sentinelErrorsArr() []error {
return []error{
gui.Errors.ErrSubProcess,
gui.Errors.ErrNoFiles,
gui.Errors.ErrSwitchRepo,
gui.Errors.ErrRestart,
}
}
// Teml is short for template used to make the required map[string]interface{} shorter when using gui.Tr.SLocalize and gui.Tr.TemplateLocalize
type Teml i18n.Teml
@@ -288,6 +297,7 @@ type guiState struct {
RefreshingFilesMutex sync.Mutex
RefreshingStatusMutex sync.Mutex
FetchMutex sync.Mutex
BranchCommitsMutex sync.Mutex
Searching searchingState
ScreenMode int
SideView *gocui.View
+5 -5
View File
@@ -1423,10 +1423,11 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Description: gui.Tr.SLocalize("editRemote"),
},
{
ViewName: "branches",
Contexts: []string{REMOTE_BRANCHES_CONTEXT_KEY},
Key: gui.getKey("universal.select"),
Handler: gui.handleCheckoutRemoteBranch,
ViewName: "branches",
Contexts: []string{REMOTE_BRANCHES_CONTEXT_KEY},
Key: gui.getKey("universal.select"),
// gonna use the exact same handler as the 'n' keybinding because everybody wants this to happen when they checkout a remote branch
Handler: gui.wrappedHandler(gui.handleNewBranchOffCurrentItem),
Description: gui.Tr.SLocalize("checkout"),
},
{
@@ -1528,7 +1529,6 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
for _, viewName := range []string{"status", "branches", "files", "commits", "commitFiles", "stash", "menu"} {
bindings = append(bindings, []*Binding{
{ViewName: viewName, Key: gui.getKey("universal.togglePanel"), Modifier: gocui.ModNone, Handler: gui.wrappedHandler(gui.nextSideWindow)},
{ViewName: viewName, Key: gui.getKey("universal.prevBlock"), Modifier: gocui.ModNone, Handler: gui.wrappedHandler(gui.previousSideWindow)},
{ViewName: viewName, Key: gui.getKey("universal.nextBlock"), Modifier: gocui.ModNone, Handler: gui.wrappedHandler(gui.nextSideWindow)},
{ViewName: viewName, Key: gui.getKey("universal.prevBlock-alt"), Modifier: gocui.ModNone, Handler: gui.wrappedHandler(gui.previousSideWindow)},
+17 -4
View File
@@ -32,7 +32,6 @@ func getFullDescriptionDisplayStringsForCommit(c *commands.Commit, cherryPickedC
yellow := color.New(color.FgYellow)
green := color.New(color.FgGreen)
blue := color.New(color.FgBlue)
cyan := color.New(color.FgCyan)
defaultColor := color.New(theme.DefaultTextColor)
diffedColor := color.New(theme.DiffTerminalColor)
@@ -66,7 +65,7 @@ func getFullDescriptionDisplayStringsForCommit(c *commands.Commit, cherryPickedC
tagString := ""
secondColumnString := blue.Sprint(utils.UnixToDate(c.UnixTimestamp))
if c.Action != "" {
secondColumnString = cyan.Sprint(c.Action)
secondColumnString = color.New(actionColorMap(c.Action)).Sprint(c.Action)
} else if c.ExtraInfo != "" {
tagColor := color.New(color.FgMagenta, color.Bold)
tagString = utils.ColoredStringDirect(c.ExtraInfo, tagColor) + " "
@@ -82,7 +81,6 @@ func getDisplayStringsForCommit(c *commands.Commit, cherryPickedCommitShaMap map
yellow := color.New(color.FgYellow)
green := color.New(color.FgGreen)
blue := color.New(color.FgBlue)
cyan := color.New(color.FgCyan)
defaultColor := color.New(theme.DefaultTextColor)
diffedColor := color.New(theme.DiffTerminalColor)
@@ -116,7 +114,7 @@ func getDisplayStringsForCommit(c *commands.Commit, cherryPickedCommitShaMap map
actionString := ""
tagString := ""
if c.Action != "" {
actionString = cyan.Sprint(utils.WithPadding(c.Action, 7)) + " "
actionString = color.New(actionColorMap(c.Action)).Sprint(utils.WithPadding(c.Action, 7)) + " "
} else if len(c.Tags) > 0 {
tagColor := color.New(color.FgMagenta, color.Bold)
tagString = utils.ColoredStringDirect(strings.Join(c.Tags, " "), tagColor) + " "
@@ -124,3 +122,18 @@ func getDisplayStringsForCommit(c *commands.Commit, cherryPickedCommitShaMap map
return []string{shaColor.Sprint(c.ShortSha()), actionString + tagString + defaultColor.Sprint(c.Name)}
}
func actionColorMap(str string) color.Attribute {
switch str {
case "pick":
return color.FgCyan
case "drop":
return color.FgRed
case "edit":
return color.FgGreen
case "fixup":
return color.FgMagenta
default:
return color.FgYellow
}
}
-11
View File
@@ -42,17 +42,6 @@ func (gui *Gui) handleRemoteBranchesEscape(g *gocui.Gui, v *gocui.View) error {
return gui.switchContext(gui.Contexts.Remotes.Context)
}
func (gui *Gui) handleCheckoutRemoteBranch(g *gocui.Gui, v *gocui.View) error {
remoteBranch := gui.getSelectedRemoteBranch()
if remoteBranch == nil {
return nil
}
if err := gui.handleCheckoutRef(remoteBranch.FullName(), handleCheckoutRefOptions{}); err != nil {
return err
}
return gui.switchContext(gui.Contexts.Branches.Context)
}
func (gui *Gui) handleMergeRemoteBranch(g *gocui.Gui, v *gocui.View) error {
selectedBranchName := gui.getSelectedRemoteBranch().FullName()
return gui.mergeBranchIntoCheckedOutBranch(selectedBranchName)
+13 -4
View File
@@ -23,11 +23,16 @@ func (gui *Gui) handleOpenSearch(g *gocui.Gui, v *gocui.View) error {
func (gui *Gui) handleSearch(g *gocui.Gui, v *gocui.View) error {
gui.State.Searching.searchString = gui.getSearchView().Buffer()
if err := gui.switchContextToView(gui.State.Searching.view.Name()); err != nil {
if err := gui.returnFromContext(); err != nil {
return err
}
if err := gui.State.Searching.view.Search(gui.State.Searching.searchString); err != nil {
view := gui.State.Searching.view
if view == nil {
return nil
}
if err := view.Search(gui.State.Searching.searchString); err != nil {
return err
}
@@ -86,9 +91,13 @@ func (gui *Gui) onSearchEscape() error {
}
func (gui *Gui) handleSearchEscape(g *gocui.Gui, v *gocui.View) error {
if err := gui.switchContextToView(gui.State.Searching.view.Name()); err != nil {
if err := gui.onSearchEscape(); err != nil {
return err
}
return gui.onSearchEscape()
if err := gui.returnFromContext(); err != nil {
return err
}
return nil
}
+1 -1
View File
@@ -77,7 +77,7 @@ func (gui *Gui) handleViewSubCommitFiles() error {
func (gui *Gui) switchToSubCommitsContext(refName string) error {
// need to populate my sub commits
builder := commands.NewCommitListBuilder(gui.Log, gui.GitCommand, gui.OSCommand, gui.Tr, gui.State.Modes.CherryPicking.CherryPickedCommits)
builder := commands.NewCommitListBuilder(gui.Log, gui.GitCommand, gui.OSCommand, gui.Tr)
commits, err := builder.GetCommits(
commands.GetCommitsOptions{
+7 -1
View File
@@ -418,6 +418,12 @@ func addDutch(i18nObject *i18n.Bundle) error {
}, &i18n.Message{
ID: "ForcePushPrompt",
Other: "Jouw branch is afgeweken van de remote branch. Druk 'esc' om te annuleren, of 'enter' om geforceert te pushen.",
}, &i18n.Message{
ID: "ForcePushDisabled",
Other: "Your branch has diverged from the remote branch and you've disabled force pushing",
}, &i18n.Message{
ID: "UpdatesRejectedAndForcePushDisabled",
Other: "Updates were rejected and you have disabled force pushing",
}, &i18n.Message{
ID: "checkForUpdate",
Other: "check voor updates",
@@ -446,7 +452,7 @@ func addDutch(i18nObject *i18n.Bundle) error {
1) Als je meer van lazygit zijn features wilt leren bekijk dan deze video:
https://youtu.be/CPLdltN7wgE
3) Als je git gebruikt, ben je een programmeur! Met jouw hulp kunnen we lazygit verbeteren, dus overweeg om een ​​donateur te worden en mee te doen aan het plezier op
2) Als je git gebruikt, ben je een programmeur! Met jouw hulp kunnen we lazygit verbeteren, dus overweeg om een ​​donateur te worden en mee te doen aan het plezier op
https://github.com/jesseduffield/lazygit`,
}, &i18n.Message{
ID: "GitconfigParseErr",
+10 -1
View File
@@ -426,6 +426,12 @@ func addEnglish(i18nObject *i18n.Bundle) error {
}, &i18n.Message{
ID: "ForcePushPrompt",
Other: "Your branch has diverged from the remote branch. Press 'esc' to cancel, or 'enter' to force push.",
}, &i18n.Message{
ID: "ForcePushDisabled",
Other: "Your branch has diverged from the remote branch and you've disabled force pushing",
}, &i18n.Message{
ID: "UpdatesRejectedAndForcePushDisabled",
Other: "Updates were rejected and you have disabled force pushing",
}, &i18n.Message{
ID: "checkForUpdate",
Other: "check for update",
@@ -454,7 +460,7 @@ func addEnglish(i18nObject *i18n.Bundle) error {
1) If you want to learn about lazygit's features, watch this vid:
https://youtu.be/CPLdltN7wgE
3) If you're using git, that makes you a programmer! With your help we can make lazygit better, so consider becoming a contributor and joining the fun at
2) If you're using git, that makes you a programmer! With your help we can make lazygit better, so consider becoming a contributor and joining the fun at
https://github.com/jesseduffield/lazygit`,
}, &i18n.Message{
ID: "GitconfigParseErr",
@@ -1176,6 +1182,9 @@ func addEnglish(i18nObject *i18n.Bundle) error {
}, &i18n.Message{
ID: "viewCommits",
Other: "view commits",
}, &i18n.Message{
ID: "minGitVersionError",
Other: "Git version must be at least 2.0 (i.e. from 2014 onwards). Please upgrade your git version. Alternatively raise an issue at https://github.com/jesseduffield/lazygit/issues for lazygit to be more backwards compatible.",
},
)
}
+6
View File
@@ -338,6 +338,12 @@ func addPolish(i18nObject *i18n.Bundle) error {
}, &i18n.Message{
ID: "ForcePushPrompt",
Other: "Twoja gałąź rozeszła się z gałęzią zdalną. Wciśnij 'esc' aby anulować lub 'enter' aby wymusić wypchnięcie.",
}, &i18n.Message{
ID: "ForcePushDisabled",
Other: "Your branch has diverged from the remote branch and you've disabled force pushing",
}, &i18n.Message{
ID: "UpdatesRejectedAndForcePushDisabled",
Other: "Updates were rejected and you have disabled force pushing",
}, &i18n.Message{
ID: "checkForUpdate",
Other: "sprawdź aktualizacje",
+27
View File
@@ -0,0 +1,27 @@
Copyright (c) 2013 Ato Araki. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of @atotto. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+48
View File
@@ -0,0 +1,48 @@
[![Build Status](https://travis-ci.org/atotto/clipboard.svg?branch=master)](https://travis-ci.org/atotto/clipboard)
[![GoDoc](https://godoc.org/github.com/atotto/clipboard?status.svg)](http://godoc.org/github.com/atotto/clipboard)
# Clipboard for Go
Provide copying and pasting to the Clipboard for Go.
Build:
$ go get github.com/atotto/clipboard
Platforms:
* OSX
* Windows 7 (probably work on other Windows)
* Linux, Unix (requires 'xclip' or 'xsel' command to be installed)
Document:
* http://godoc.org/github.com/atotto/clipboard
Notes:
* Text string only
* UTF-8 text encoding only (no conversion)
TODO:
* Clipboard watcher(?)
## Commands:
paste shell command:
$ go get github.com/atotto/clipboard/cmd/gopaste
$ # example:
$ gopaste > document.txt
copy shell command:
$ go get github.com/atotto/clipboard/cmd/gocopy
$ # example:
$ cat document.txt | gocopy
+20
View File
@@ -0,0 +1,20 @@
// Copyright 2013 @atotto. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package clipboard read/write on clipboard
package clipboard
// ReadAll read string from clipboard
func ReadAll() (string, error) {
return readAll()
}
// WriteAll write string to clipboard
func WriteAll(text string) error {
return writeAll(text)
}
// Unsupported might be set true during clipboard init, to help callers decide
// whether or not to offer clipboard options.
var Unsupported bool
+52
View File
@@ -0,0 +1,52 @@
// Copyright 2013 @atotto. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build darwin
package clipboard
import (
"os/exec"
)
var (
pasteCmdArgs = "pbpaste"
copyCmdArgs = "pbcopy"
)
func getPasteCommand() *exec.Cmd {
return exec.Command(pasteCmdArgs)
}
func getCopyCommand() *exec.Cmd {
return exec.Command(copyCmdArgs)
}
func readAll() (string, error) {
pasteCmd := getPasteCommand()
out, err := pasteCmd.Output()
if err != nil {
return "", err
}
return string(out), nil
}
func writeAll(text string) error {
copyCmd := getCopyCommand()
in, err := copyCmd.StdinPipe()
if err != nil {
return err
}
if err := copyCmd.Start(); err != nil {
return err
}
if _, err := in.Write([]byte(text)); err != nil {
return err
}
if err := in.Close(); err != nil {
return err
}
return copyCmd.Wait()
}
+129
View File
@@ -0,0 +1,129 @@
// Copyright 2013 @atotto. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build freebsd linux netbsd openbsd solaris dragonfly
package clipboard
import (
"errors"
"os"
"os/exec"
)
const (
xsel = "xsel"
xclip = "xclip"
wlcopy = "wl-copy"
wlpaste = "wl-paste"
termuxClipboardGet = "termux-clipboard-get"
termuxClipboardSet = "termux-clipboard-set"
)
var (
Primary bool
pasteCmdArgs []string
copyCmdArgs []string
xselPasteArgs = []string{xsel, "--output", "--clipboard"}
xselCopyArgs = []string{xsel, "--input", "--clipboard"}
xclipPasteArgs = []string{xclip, "-out", "-selection", "clipboard"}
xclipCopyArgs = []string{xclip, "-in", "-selection", "clipboard"}
wlpasteArgs = []string{wlpaste, "--no-newline"}
wlcopyArgs = []string{wlcopy}
termuxPasteArgs = []string{termuxClipboardGet}
termuxCopyArgs = []string{termuxClipboardSet}
missingCommands = errors.New("No clipboard utilities available. Please install xsel, xclip, wl-clipboard or Termux:API add-on for termux-clipboard-get/set.")
)
func init() {
if os.Getenv("WAYLAND_DISPLAY") != "" {
pasteCmdArgs = wlpasteArgs;
copyCmdArgs = wlcopyArgs;
if _, err := exec.LookPath(wlcopy); err == nil {
if _, err := exec.LookPath(wlpaste); err == nil {
return
}
}
}
pasteCmdArgs = xclipPasteArgs
copyCmdArgs = xclipCopyArgs
if _, err := exec.LookPath(xclip); err == nil {
return
}
pasteCmdArgs = xselPasteArgs
copyCmdArgs = xselCopyArgs
if _, err := exec.LookPath(xsel); err == nil {
return
}
pasteCmdArgs = termuxPasteArgs
copyCmdArgs = termuxCopyArgs
if _, err := exec.LookPath(termuxClipboardSet); err == nil {
if _, err := exec.LookPath(termuxClipboardGet); err == nil {
return
}
}
Unsupported = true
}
func getPasteCommand() *exec.Cmd {
if Primary {
pasteCmdArgs = pasteCmdArgs[:1]
}
return exec.Command(pasteCmdArgs[0], pasteCmdArgs[1:]...)
}
func getCopyCommand() *exec.Cmd {
if Primary {
copyCmdArgs = copyCmdArgs[:1]
}
return exec.Command(copyCmdArgs[0], copyCmdArgs[1:]...)
}
func readAll() (string, error) {
if Unsupported {
return "", missingCommands
}
pasteCmd := getPasteCommand()
out, err := pasteCmd.Output()
if err != nil {
return "", err
}
return string(out), nil
}
func writeAll(text string) error {
if Unsupported {
return missingCommands
}
copyCmd := getCopyCommand()
in, err := copyCmd.StdinPipe()
if err != nil {
return err
}
if err := copyCmd.Start(); err != nil {
return err
}
if _, err := in.Write([]byte(text)); err != nil {
return err
}
if err := in.Close(); err != nil {
return err
}
return copyCmd.Wait()
}
+128
View File
@@ -0,0 +1,128 @@
// Copyright 2013 @atotto. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build windows
package clipboard
import (
"syscall"
"time"
"unsafe"
)
const (
cfUnicodetext = 13
gmemMoveable = 0x0002
)
var (
user32 = syscall.MustLoadDLL("user32")
openClipboard = user32.MustFindProc("OpenClipboard")
closeClipboard = user32.MustFindProc("CloseClipboard")
emptyClipboard = user32.MustFindProc("EmptyClipboard")
getClipboardData = user32.MustFindProc("GetClipboardData")
setClipboardData = user32.MustFindProc("SetClipboardData")
kernel32 = syscall.NewLazyDLL("kernel32")
globalAlloc = kernel32.NewProc("GlobalAlloc")
globalFree = kernel32.NewProc("GlobalFree")
globalLock = kernel32.NewProc("GlobalLock")
globalUnlock = kernel32.NewProc("GlobalUnlock")
lstrcpy = kernel32.NewProc("lstrcpyW")
)
// waitOpenClipboard opens the clipboard, waiting for up to a second to do so.
func waitOpenClipboard() error {
started := time.Now()
limit := started.Add(time.Second)
var r uintptr
var err error
for time.Now().Before(limit) {
r, _, err = openClipboard.Call(0)
if r != 0 {
return nil
}
time.Sleep(time.Millisecond)
}
return err
}
func readAll() (string, error) {
err := waitOpenClipboard()
if err != nil {
return "", err
}
defer closeClipboard.Call()
h, _, err := getClipboardData.Call(cfUnicodetext)
if h == 0 {
return "", err
}
l, _, err := globalLock.Call(h)
if l == 0 {
return "", err
}
text := syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(l))[:])
r, _, err := globalUnlock.Call(h)
if r == 0 {
return "", err
}
return text, nil
}
func writeAll(text string) error {
err := waitOpenClipboard()
if err != nil {
return err
}
defer closeClipboard.Call()
r, _, err := emptyClipboard.Call(0)
if r == 0 {
return err
}
data := syscall.StringToUTF16(text)
// "If the hMem parameter identifies a memory object, the object must have
// been allocated using the function with the GMEM_MOVEABLE flag."
h, _, err := globalAlloc.Call(gmemMoveable, uintptr(len(data)*int(unsafe.Sizeof(data[0]))))
if h == 0 {
return err
}
defer func() {
if h != 0 {
globalFree.Call(h)
}
}()
l, _, err := globalLock.Call(h)
if l == 0 {
return err
}
r, _, err = lstrcpy.Call(l, uintptr(unsafe.Pointer(&data[0])))
if r == 0 {
return err
}
r, _, err = globalUnlock.Call(h)
if r == 0 {
if err.(syscall.Errno) != 0 {
return err
}
}
r, _, err = setClipboardData.Call(cfUnicodetext, h)
if r == 0 {
return err
}
h = 0 // suppress deferred cleanup
return nil
}
+1
View File
@@ -0,0 +1 @@
module github.com/atotto/clipboard
+3
View File
@@ -1,3 +1,6 @@
# github.com/atotto/clipboard v0.1.2
## explicit
github.com/atotto/clipboard
# github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21
## explicit
github.com/cloudfoundry/jibber_jabber