Repository: jesseduffield/lazygit (language: go)

Issue:
No newline at end of file when adding to .gitignore
Lazygit does not add a trailing newline character when modifying `.gitignore`.

It should definitely do that on Unixes, personally I think it can't hurt on Windows either.


Discussion / hints:
Could you clarify when does this happen?

Does `lazygit` actively edit the `.gitignore` somehow or does it just not register the trailing `\n` or is it something else?
There is an option "ignore this file" in the file tab.
When you press it (i think it's bound to `i`?), lazygit will edit the
.gitignore file and add an entry for the selected file.
While doing this, it does not add a newline at end.

On Mon, 13 Jun 2022 at 23:39, Luka Markušić ***@***.***>
wrote:

> Could you clarify when does this happen?
>
> Does lazygit actively edit the .gitignore somehow or does it just not
> register the trailing \n or is it something else?
>
> —
> Reply to this email directly, view it on GitHub
> <https://github.com/jesseduffield/lazygit/issues/1898#issuecomment-1154465463>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AAERQ2MBV3JZBCHCD24VBRTVO6S75ANCNFSM5UR4OUAA>
> .
> You are receiving this because you authored the thread.Message ID:
> ***@***.***>
>


Fix patch:
diff --git a/pkg/commands/oscommands/os.go b/pkg/commands/oscommands/os.go
index b9ea928be18..2a7cc132858 100644
--- a/pkg/commands/oscommands/os.go
+++ b/pkg/commands/oscommands/os.go
@@ -109,13 +109,34 @@ func (c *OSCommand) Quote(message string) string {
 // AppendLineToFile adds a new line in file
 func (c *OSCommand) AppendLineToFile(filename, line string) error {
 	c.LogCommand(fmt.Sprintf("Appending '%s' to file '%s'", line, filename), false)
-	f, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0o600)
+	f, err := os.OpenFile(filename, os.O_APPEND|os.O_RDWR|os.O_CREATE, 0o600)
 	if err != nil {
 		return utils.WrapError(err)
 	}
 	defer f.Close()
 
-	_, err = f.WriteString("\n" + line)
+	info, err := os.Stat(filename)
+	if err != nil {
+		return utils.WrapError(err)
+	}
+
+	if info.Size() > 0 {
+		// read last char
+		buf := make([]byte, 1)
+		if _, err := f.ReadAt(buf, info.Size()-1); err != nil {
+			return utils.WrapError(err)
+		}
+
+		// if the last byte of the file is not a newline, add it
+		if []byte("\n")[0] != buf[0] {
+			_, err = f.WriteString("\n")
+		}
+	}
+
+	if err == nil {
+		_, err = f.WriteString(line + "\n")
+	}
+
 	if err != nil {
 		return utils.WrapError(err)
 	}


Test patch:
diff --git a/pkg/commands/oscommands/os_test.go b/pkg/commands/oscommands/os_test.go
index 0152fec586d..9692244057b 100644
--- a/pkg/commands/oscommands/os_test.go
+++ b/pkg/commands/oscommands/os_test.go
@@ -1,7 +1,9 @@
 package oscommands
 
 import (
+	"io/ioutil"
 	"os"
+	"path/filepath"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -135,3 +137,61 @@ func TestOSCommandFileType(t *testing.T) {
 		_ = os.RemoveAll(s.path)
 	}
 }
+
+func TestOSCommandAppendLineToFile(t *testing.T) {
+	type scenario struct {
+		path  string
+		setup func(string)
+		test  func(string)
+	}
+
+	scenarios := []scenario{
+		{
+			filepath.Join(os.TempDir(), "testFile"),
+			func(path string) {
+				if err := ioutil.WriteFile(path, []byte("hello"), 0o600); err != nil {
+					panic(err)
+				}
+			},
+			func(output string) {
+				assert.EqualValues(t, "hello\nworld\n", output)
+			},
+		},
+		{
+			filepath.Join(os.TempDir(), "emptyTestFile"),
+			func(path string) {
+				if err := ioutil.WriteFile(path, []byte(""), 0o600); err != nil {
+					panic(err)
+				}
+			},
+			func(output string) {
+				assert.EqualValues(t, "world\n", output)
+			},
+		},
+		{
+			filepath.Join(os.TempDir(), "testFileWithNewline"),
+			func(path string) {
+				if err := ioutil.WriteFile(path, []byte("hello\n"), 0o600); err != nil {
+					panic(err)
+				}
+			},
+			func(output string) {
+				assert.EqualValues(t, "hello\nworld\n", output)
+			},
+		},
+	}
+
+	for _, s := range scenarios {
+		s.setup(s.path)
+		osCommand := NewDummyOSCommand()
+		if err := osCommand.AppendLineToFile(s.path, "world"); err != nil {
+			panic(err)
+		}
+		f, err := ioutil.ReadFile(s.path)
+		if err != nil {
+			panic(err)
+		}
+		s.test(string(f))
+		_ = os.RemoveAll(s.path)
+	}
+}
diff --git a/test/integration/gitignoreMenu/expected/repo/.git_keep/config b/test/integration/gitignoreMenu/expected/repo/.git_keep/config
index 8ae104545b4..596ebaeb32a 100644
--- a/test/integration/gitignoreMenu/expected/repo/.git_keep/config
+++ b/test/integration/gitignoreMenu/expected/repo/.git_keep/config
@@ -3,8 +3,6 @@
 	filemode = true
 	bare = false
 	logallrefupdates = true
-	ignorecase = true
-	precomposeunicode = true
 [user]
 	email = CI@example.com
 	name = CI
diff --git a/test/integration/gitignoreMenu/expected/repo/.git_keep/info/exclude b/test/integration/gitignoreMenu/expected/repo/.git_keep/info/exclude
index 8e9f2071f5d..a5196d1be8f 100644
--- a/test/integration/gitignoreMenu/expected/repo/.git_keep/info/exclude
+++ b/test/integration/gitignoreMenu/expected/repo/.git_keep/info/exclude
@@ -4,4 +4,3 @@
 # exclude patterns (uncomment them if you want to use them):
 # *.[oa]
 # *~
-.DS_Store
diff --git a/test/integration/gitignoreMenu/expected/repo/.git_keep/logs/HEAD b/test/integration/gitignoreMenu/expected/repo/.git_keep/logs/HEAD
index bef74759d19..329f08e984b 100644
--- a/test/integration/gitignoreMenu/expected/repo/.git_keep/logs/HEAD
+++ b/test/integration/gitignoreMenu/expected/repo/.git_keep/logs/HEAD
@@ -1,1 +1,1 @@
-0000000000000000000000000000000000000000 9dd04ee245b7d6f1f80aa2b428111cbac4a4e37d CI <CI@example.com> 1657012500 +1000	commit (initial): Initial commit
+0000000000000000000000000000000000000000 04535177acab8a81c84b0b1b44ee3aea76b0e36e CI <CI@example.com> 1659528492 +0200	commit (initial): Initial commit
diff --git a/test/integration/gitignoreMenu/expected/repo/.git_keep/logs/refs/heads/master b/test/integration/gitignoreMenu/expected/repo/.git_keep/logs/refs/heads/master
index bef74759d19..329f08e984b 100644
--- a/test/integration/gitignoreMenu/expected/repo/.git_keep/logs/refs/heads/master
+++ b/test/integration/gitignoreMenu/expected/repo/.git_keep/logs/refs/heads/master
@@ -1,1 +1,1 @@
-0000000000000000000000000000000000000000 9dd04ee245b7d6f1f80aa2b428111cbac4a4e37d CI <CI@example.com> 1657012500 +1000	commit (initial): Initial commit
+0000000000000000000000000000000000000000 04535177acab8a81c84b0b1b44ee3aea76b0e36e CI <CI@example.com> 1659528492 +0200	commit (initial): Initial commit
diff --git a/test/integration/gitignoreMenu/expected/repo/.git_keep/objects/04/535177acab8a81c84b0b1b44ee3aea76b0e36e b/test/integration/gitignoreMenu/expected/repo/.git_keep/objects/04/535177acab8a81c84b0b1b44ee3aea76b0e36e
new file mode 100644
index 00000000000..ff7e7bc39b6
Binary files /dev/null and b/test/integration/gitignoreMenu/expected/repo/.git_keep/objects/04/535177acab8a81c84b0b1b44ee3aea76b0e36e differ
diff --git a/test/integration/gitignoreMenu/expected/repo/.git_keep/objects/9d/d04ee245b7d6f1f80aa2b428111cbac4a4e37d b/test/integration/gitignoreMenu/expected/repo/.git_keep/objects/9d/d04ee245b7d6f1f80aa2b428111cbac4a4e37d
deleted file mode 100644
index eb76d5d8805..00000000000
Binary files a/test/integration/gitignoreMenu/expected/repo/.git_keep/objects/9d/d04ee245b7d6f1f80aa2b428111cbac4a4e37d and /dev/null differ
diff --git a/test/integration/gitignoreMenu/expected/repo/.git_keep/refs/heads/master b/test/integration/gitignoreMenu/expected/repo/.git_keep/refs/heads/master
index 998d07e0e0e..4725f51827a 100644
--- a/test/integration/gitignoreMenu/expected/repo/.git_keep/refs/heads/master
+++ b/test/integration/gitignoreMenu/expected/repo/.git_keep/refs/heads/master
@@ -1,1 +1,1 @@
-9dd04ee245b7d6f1f80aa2b428111cbac4a4e37d
+04535177acab8a81c84b0b1b44ee3aea76b0e36e
diff --git a/test/integration/gitignoreMenu/expected/repo/lg_ignore_file b/test/integration/gitignoreMenu/expected/repo/lg_ignore_file
index 959aad47924..3829ab872c8 100644
--- a/test/integration/gitignoreMenu/expected/repo/lg_ignore_file
+++ b/test/integration/gitignoreMenu/expected/repo/lg_ignore_file
@@ -1,2 +1,1 @@
-
-myfile1
\ No newline at end of file
+myfile1