Skip to content

Commit 774a6d8

Browse files
authored
Merge pull request zyedidia#3822 from dmaluka/fix-spurious-backups
Fix spurious backups of unmodified files
2 parents c9f84cd + 0a9fa4f commit 774a6d8

File tree

6 files changed

+141
-131
lines changed

6 files changed

+141
-131
lines changed

cmd/micro/micro.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -352,9 +352,11 @@ func main() {
352352
} else {
353353
fmt.Println("Micro encountered an error:", errors.Wrap(err, 2).ErrorStack(), "\nIf you can reproduce this error, please report it at https://github.com/zyedidia/micro/issues")
354354
}
355-
// backup all open buffers
355+
// immediately backup all buffers with unsaved changes
356356
for _, b := range buffer.OpenBuffers {
357-
b.Backup()
357+
if b.Modified() {
358+
b.Backup()
359+
}
358360
}
359361
exit(1)
360362
}
@@ -489,8 +491,6 @@ func DoEvent() {
489491
}
490492
case f := <-timerChan:
491493
f()
492-
case b := <-buffer.BackupCompleteChan:
493-
b.RequestedBackup = false
494494
case <-sighup:
495495
exit(0)
496496
case <-util.Sigterm:

cmd/micro/micro_test.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,11 @@ func startup(args []string) (tcell.SimulationScreen, error) {
5555
if err := recover(); err != nil {
5656
screen.Screen.Fini()
5757
fmt.Println("Micro encountered an error:", err)
58-
// backup all open buffers
58+
// immediately backup all buffers with unsaved changes
5959
for _, b := range buffer.OpenBuffers {
60-
b.Backup()
60+
if b.Modified() {
61+
b.Backup()
62+
}
6163
}
6264
// Print the stack trace too
6365
log.Fatalf(errors.Wrap(err, 2).ErrorStack())

internal/buffer/backup.go

Lines changed: 64 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -34,75 +34,105 @@ Options: [r]ecover, [i]gnore, [a]bort: `
3434

3535
const backupSeconds = 8
3636

37-
var BackupCompleteChan chan *Buffer
37+
type backupRequestType int
38+
39+
const (
40+
backupCreate = iota
41+
backupRemove
42+
)
43+
44+
type backupRequest struct {
45+
buf *SharedBuffer
46+
reqType backupRequestType
47+
}
48+
49+
var requestedBackups map[*SharedBuffer]bool
3850

3951
func init() {
40-
BackupCompleteChan = make(chan *Buffer, 10)
52+
requestedBackups = make(map[*SharedBuffer]bool)
53+
}
54+
55+
func (b *SharedBuffer) RequestBackup() {
56+
backupRequestChan <- backupRequest{buf: b, reqType: backupCreate}
4157
}
4258

43-
func (b *Buffer) RequestBackup() {
44-
if !b.RequestedBackup {
45-
select {
46-
case backupRequestChan <- b:
47-
default:
48-
// channel is full
59+
func (b *SharedBuffer) CancelBackup() {
60+
backupRequestChan <- backupRequest{buf: b, reqType: backupRemove}
61+
}
62+
63+
func handleBackupRequest(br backupRequest) {
64+
switch br.reqType {
65+
case backupCreate:
66+
// schedule periodic backup
67+
requestedBackups[br.buf] = true
68+
case backupRemove:
69+
br.buf.RemoveBackup()
70+
delete(requestedBackups, br.buf)
71+
}
72+
}
73+
74+
func periodicBackup() {
75+
for buf := range requestedBackups {
76+
err := buf.Backup()
77+
if err == nil {
78+
delete(requestedBackups, buf)
4979
}
50-
b.RequestedBackup = true
5180
}
5281
}
5382

54-
func (b *Buffer) backupDir() string {
83+
func (b *SharedBuffer) backupDir() string {
5584
backupdir, err := util.ReplaceHome(b.Settings["backupdir"].(string))
5685
if backupdir == "" || err != nil {
5786
backupdir = filepath.Join(config.ConfigDir, "backups")
5887
}
5988
return backupdir
6089
}
6190

62-
func (b *Buffer) keepBackup() bool {
91+
func (b *SharedBuffer) keepBackup() bool {
6392
return b.forceKeepBackup || b.Settings["permbackup"].(bool)
6493
}
6594

66-
// Backup saves the current buffer to the backups directory
67-
func (b *Buffer) Backup() error {
68-
if !b.Settings["backup"].(bool) || b.Path == "" || b.Type != BTDefault {
69-
return nil
70-
}
71-
95+
func (b *SharedBuffer) writeBackup(path string) (string, error) {
7296
backupdir := b.backupDir()
73-
if _, err := os.Stat(backupdir); errors.Is(err, fs.ErrNotExist) {
74-
os.Mkdir(backupdir, os.ModePerm)
75-
}
76-
77-
name := util.DetermineEscapePath(backupdir, b.AbsPath)
78-
if _, err := os.Stat(name); errors.Is(err, fs.ErrNotExist) {
79-
_, err = b.overwriteFile(name)
80-
if err == nil {
81-
BackupCompleteChan <- b
97+
if _, err := os.Stat(backupdir); err != nil {
98+
if !errors.Is(err, fs.ErrNotExist) {
99+
return "", err
100+
}
101+
if err = os.Mkdir(backupdir, os.ModePerm); err != nil {
102+
return "", err
82103
}
83-
return err
84104
}
85105

106+
name := util.DetermineEscapePath(backupdir, path)
86107
tmp := util.AppendBackupSuffix(name)
108+
87109
_, err := b.overwriteFile(tmp)
88110
if err != nil {
89111
os.Remove(tmp)
90-
return err
112+
return name, err
91113
}
92114
err = os.Rename(tmp, name)
93115
if err != nil {
94116
os.Remove(tmp)
95-
return err
117+
return name, err
96118
}
97119

98-
BackupCompleteChan <- b
120+
return name, nil
121+
}
122+
123+
// Backup saves the buffer to the backups directory
124+
func (b *SharedBuffer) Backup() error {
125+
if !b.Settings["backup"].(bool) || b.Path == "" || b.Type != BTDefault {
126+
return nil
127+
}
99128

129+
_, err := b.writeBackup(b.AbsPath)
100130
return err
101131
}
102132

103133
// RemoveBackup removes any backup file associated with this buffer
104-
func (b *Buffer) RemoveBackup() {
105-
if !b.Settings["backup"].(bool) || b.keepBackup() || b.Path == "" || b.Type != BTDefault {
134+
func (b *SharedBuffer) RemoveBackup() {
135+
if b.keepBackup() || b.Path == "" || b.Type != BTDefault {
106136
return
107137
}
108138
f := util.DetermineEscapePath(b.backupDir(), b.AbsPath)
@@ -111,7 +141,7 @@ func (b *Buffer) RemoveBackup() {
111141

112142
// ApplyBackup applies the corresponding backup file to this buffer (if one exists)
113143
// Returns true if a backup was applied
114-
func (b *Buffer) ApplyBackup(fsize int64) (bool, bool) {
144+
func (b *SharedBuffer) ApplyBackup(fsize int64) (bool, bool) {
115145
if b.Settings["backup"].(bool) && !b.Settings["permbackup"].(bool) && len(b.Path) > 0 && b.Type == BTDefault {
116146
backupfile := util.DetermineEscapePath(b.backupDir(), b.AbsPath)
117147
if info, err := os.Stat(backupfile); err == nil {
@@ -125,7 +155,7 @@ func (b *Buffer) ApplyBackup(fsize int64) (bool, bool) {
125155
if choice%3 == 0 {
126156
// recover
127157
b.LineArray = NewLineArray(uint64(fsize), FFAuto, backup)
128-
b.isModified = true
158+
b.setModified()
129159
return true, true
130160
} else if choice%3 == 1 {
131161
// delete

internal/buffer/buffer.go

Lines changed: 49 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import (
1313
"strconv"
1414
"strings"
1515
"sync"
16-
"sync/atomic"
1716
"time"
1817

1918
luar "layeh.com/gopher-luar"
@@ -101,7 +100,6 @@ type SharedBuffer struct {
101100
diffLock sync.RWMutex
102101
diff map[int]DiffStatus
103102

104-
RequestedBackup bool
105103
forceKeepBackup bool
106104

107105
// ReloadDisabled allows the user to disable reloads if they
@@ -126,20 +124,62 @@ type SharedBuffer struct {
126124
}
127125

128126
func (b *SharedBuffer) insert(pos Loc, value []byte) {
129-
b.isModified = true
130127
b.HasSuggestions = false
131128
b.LineArray.insert(pos, value)
129+
b.setModified()
132130

133131
inslines := bytes.Count(value, []byte{'\n'})
134132
b.MarkModified(pos.Y, pos.Y+inslines)
135133
}
134+
136135
func (b *SharedBuffer) remove(start, end Loc) []byte {
137-
b.isModified = true
138136
b.HasSuggestions = false
137+
defer b.setModified()
139138
defer b.MarkModified(start.Y, end.Y)
140139
return b.LineArray.remove(start, end)
141140
}
142141

142+
func (b *SharedBuffer) setModified() {
143+
if b.Type.Scratch {
144+
return
145+
}
146+
147+
if b.Settings["fastdirty"].(bool) {
148+
b.isModified = true
149+
} else {
150+
var buff [md5.Size]byte
151+
152+
b.calcHash(&buff)
153+
b.isModified = buff != b.origHash
154+
}
155+
156+
if b.isModified {
157+
b.RequestBackup()
158+
} else {
159+
b.CancelBackup()
160+
}
161+
}
162+
163+
// calcHash calculates md5 hash of all lines in the buffer
164+
func (b *SharedBuffer) calcHash(out *[md5.Size]byte) {
165+
h := md5.New()
166+
167+
if len(b.lines) > 0 {
168+
h.Write(b.lines[0].data)
169+
170+
for _, l := range b.lines[1:] {
171+
if b.Endings == FFDos {
172+
h.Write([]byte{'\r', '\n'})
173+
} else {
174+
h.Write([]byte{'\n'})
175+
}
176+
h.Write(l.data)
177+
}
178+
}
179+
180+
h.Sum((*out)[:0])
181+
}
182+
143183
// MarkModified marks the buffer as modified for this frame
144184
// and performs rehighlighting if syntax highlighting is enabled
145185
func (b *SharedBuffer) MarkModified(start, end int) {
@@ -187,7 +227,6 @@ type Buffer struct {
187227
*EventHandler
188228
*SharedBuffer
189229

190-
fini int32
191230
cursors []*Cursor
192231
curCursor int
193232
StartCursor Loc
@@ -416,7 +455,7 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
416455
} else if !hasBackup {
417456
// since applying a backup does not save the applied backup to disk, we should
418457
// not calculate the original hash based on the backup data
419-
calcHash(b, &b.origHash)
458+
b.calcHash(&b.origHash)
420459
}
421460
}
422461

@@ -458,13 +497,11 @@ func (b *Buffer) Fini() {
458497
if !b.Modified() {
459498
b.Serialize()
460499
}
461-
b.RemoveBackup()
500+
b.CancelBackup()
462501

463502
if b.Type == BTStdout {
464503
fmt.Fprint(util.Stdout, string(b.Bytes()))
465504
}
466-
467-
atomic.StoreInt32(&(b.fini), int32(1))
468505
}
469506

470507
// GetName returns the name that should be displayed in the statusline
@@ -494,8 +531,6 @@ func (b *Buffer) Insert(start Loc, text string) {
494531
b.EventHandler.cursors = b.cursors
495532
b.EventHandler.active = b.curCursor
496533
b.EventHandler.Insert(start, text)
497-
498-
b.RequestBackup()
499534
}
500535
}
501536

@@ -505,8 +540,6 @@ func (b *Buffer) Remove(start, end Loc) {
505540
b.EventHandler.cursors = b.cursors
506541
b.EventHandler.active = b.curCursor
507542
b.EventHandler.Remove(start, end)
508-
509-
b.RequestBackup()
510543
}
511544
}
512545

@@ -558,7 +591,7 @@ func (b *Buffer) ReOpen() error {
558591
if len(data) > LargeFileThreshold {
559592
b.Settings["fastdirty"] = true
560593
} else {
561-
calcHash(b, &b.origHash)
594+
b.calcHash(&b.origHash)
562595
}
563596
}
564597
b.isModified = false
@@ -633,18 +666,7 @@ func (b *Buffer) Shared() bool {
633666
// Modified returns if this buffer has been modified since
634667
// being opened
635668
func (b *Buffer) Modified() bool {
636-
if b.Type.Scratch {
637-
return false
638-
}
639-
640-
if b.Settings["fastdirty"].(bool) {
641-
return b.isModified
642-
}
643-
644-
var buff [md5.Size]byte
645-
646-
calcHash(b, &buff)
647-
return buff != b.origHash
669+
return b.isModified
648670
}
649671

650672
// Size returns the number of bytes in the current buffer
@@ -663,26 +685,6 @@ func (b *Buffer) Size() int {
663685
return nb
664686
}
665687

666-
// calcHash calculates md5 hash of all lines in the buffer
667-
func calcHash(b *Buffer, out *[md5.Size]byte) {
668-
h := md5.New()
669-
670-
if len(b.lines) > 0 {
671-
h.Write(b.lines[0].data)
672-
673-
for _, l := range b.lines[1:] {
674-
if b.Endings == FFDos {
675-
h.Write([]byte{'\r', '\n'})
676-
} else {
677-
h.Write([]byte{'\n'})
678-
}
679-
h.Write(l.data)
680-
}
681-
}
682-
683-
h.Sum((*out)[:0])
684-
}
685-
686688
func parseDefFromFile(f config.RuntimeFile, header *highlight.Header) *highlight.Def {
687689
data, err := f.Data()
688690
if err != nil {
@@ -1233,7 +1235,6 @@ func (b *Buffer) FindMatchingBrace(start Loc) (Loc, bool, bool) {
12331235
func (b *Buffer) Retab() {
12341236
toSpaces := b.Settings["tabstospaces"].(bool)
12351237
tabsize := util.IntOpt(b.Settings["tabsize"])
1236-
dirty := false
12371238

12381239
for i := 0; i < b.LinesNum(); i++ {
12391240
l := b.LineBytes(i)
@@ -1254,10 +1255,9 @@ func (b *Buffer) Retab() {
12541255
b.Unlock()
12551256

12561257
b.MarkModified(i, i)
1257-
dirty = true
12581258
}
12591259

1260-
b.isModified = dirty
1260+
b.setModified()
12611261
}
12621262

12631263
// ParseCursorLocation turns a cursor location like 10:5 (LINE:COL)

0 commit comments

Comments
 (0)