Emacs의 자동 저장 및 복구 기능을 좀 더 잘 활용하자
이맥스는 기본적으로 버퍼의 변경점을 자동으로 저장하는 기능이 있다. 물론 강제로 M-x do-auto-save RET 명령어를 사용해서 자동 저장을 시키는 방법도 있지만 보통은 주기적으로 현재 파일의 경로에 # 접두사와 접미사를 붙여서, 즉 test.txt 파일이라면 #test.txt#와 같은 파일명으로 파일을 저장한다.
그러나 사실 로컬에서야 그다지 문제가 되지 않지만, tramp를 커넥션이 느리거나 네트워크 상황이 좋지 않은 곳에서 사용할 때 가끔 자동 저장 파일을 저장하겠다고 이맥스가 미쳐 날 뛰는 상황을 경험하게 된다. 그래서 자동 저장 파일을 파일의 원래 위치에 만드는 것 보다는 특정 디렉토리 한 곳에서 생성하도록 하는 것이 유리한 측면이 많다.
;; store backup files in .emacs.d/backups
(defconst emacs-backup-directory "~/.emacs.d/backups/")
(setq backup-directory-alist `((".*" . ,emacs-backup-directory))
auto-save-file-name-transforms `((".*" ,emacs-backup-directory t)))
이렇게 설정하게 되면, 모든 자동 저장 파일은 $HOME/.emacs.d/backups 아래에 저장되게 된다. 물론 추가적으로 이렇게 자동 저장 파일을 한 곳으로 몰아 두게 되면 파일 시스템 이곳 저곳에 남아 있는 자동 저장 파일의 흔적들을 굳이 찾아서 지울 필요 없이, 그냥 $HOME/.emacs.d/backups 아래에 있는 파일들만 알아서 지워 주면 되니 그런 측면에서도 꽤 유용하다.
/home/ysjang/.emacs.d/backups: total used in directory 28 available 403893348 drwx------ 2 ysjang ysjang 4096 4월 4 11:48 . drwxrwxr-x 15 ysjang ysjang 4096 4월 4 11:28 .. -rw-rw-r-- 1 ysjang ysjang 3073 4월 4 12:52 #!home!ysjang!blog!review-recover-file.html# -rw-rw-r-- 1 ysjang ysjang 2009 4월 1 14:57 #!home!ysjang!Downloads!shBrushLisp.js# -rw-rw-r-- 1 ysjang ysjang 12 3월 31 15:22 #!home!ysjang!.emacs.d!test.html# -rw-rw-r-- 1 ysjang ysjang 30 4월 4 09:32 #!home!ysjang!.emacs.d!test.org#
자동 저장을 했으면 이 내용을 바탕으로 복구를 할 수 있어야 할것이다. 자동 저장 기능을 켜 둔 채로 이맥스를 쓰다 보면, 파일을 새로 열 때 shBrushLisp.js has auto save data; consider M-x recover-this-file와 같이 복구 가능하다는 메시지가 표시 되는 것을 본 적이 있을 것이다. 이 것은 자동 저장된 파일이 존재할 경우 나타나는 메시지로써 해당 버퍼에서 M-x recover-this-file RET이나 M-x recover-file RET 파일명 RET을 입력함으로써 auto save된 파일의 내용으로 현재 버퍼를 복구할 수 있다.
다만 위의 그림처럼 보듯이, 파일의 저장시간을 표시하는 것만으로는 그다지 유용하다는 느낌을 받을 수 없다. 이전에 kill-buffer나 save-some-buffers에서 변경점을 보여 주듯이 recove-file을 할 때에도 현재 버퍼와 자동 저장된 파일을 비교해서 표시해준다면 확실히 그 자리에서 복구 여부를 판단하기 쉬울 것이다. recover-file의 경우 advice function으로는 정의하기 난감해서 이전에 save-some-buffers때 처럼, 이맥스 기본 lisp library의 files.el을 가져와서 고쳐서 정의하도록 했다.
;; to override recover-file (from 24.5.5 files.el)
(defun my:recover-file (file)
"Visit file FILE, but get contents from its last auto-save file."
;; Actually putting the file name in the minibuffer should be used
;; only rarely.
;; Not just because users often use the default.
(interactive "FRecover file: ")
(setq file (expand-file-name file))
(if (auto-save-file-name-p (file-name-nondirectory file))
(error "%s is an auto-save file" (abbreviate-file-name file)))
(let ((file-name (let ((buffer-file-name file))
(make-auto-save-file-name))))
(cond ((if (file-exists-p file)
(not (file-newer-than-file-p file-name file))
(not (file-exists-p file-name)))
(error "Auto-save file %s not current"
(abbreviate-file-name file-name)))
((save-window-excursion
(with-temp-buffer-window
"*Directory*" nil
#'(lambda (window _value)
(with-selected-window window
(unwind-protect
(yes-or-no-p (format "Recover auto save file %s? " file-name))
(when (window-live-p window)
(quit-restore-window window 'kill)))))
(let* ((file-buffer (find-buffer-visiting file))
(diff-switches "-urN"))
(display-buffer file-buffer '(display-buffer-same-window))
(delete-other-windows)
(diff file-buffer file-name nil 'noasync)
(with-current-buffer standard-output
(let ((switches dired-listing-switches))
(if (file-symlink-p file)
(setq switches (concat switches " -L")))
;; Use insert-directory-safely, not insert-directory,
;; because these files might not exist. In particular,
;; FILE might not exist if the auto-save file was for
;; a buffer that didn't visit a file, such as "*mail*".
;; The code in v20.x called `ls' directly, so we need
;; to emulate what `ls' did in that case.
(insert-directory-safely file switches)
(insert-directory-safely file-name switches)))
(fit-window-to-buffer
(select-window (display-buffer standard-output '(display-buffer-at-bottom)))))))
(switch-to-buffer (find-file-noselect file t))
(let ((inhibit-read-only t)
;; Keep the current buffer-file-coding-system.
(coding-system buffer-file-coding-system)
;; Auto-saved file should be read with special coding.
(coding-system-for-read 'auto-save-coding))
(erase-buffer)
(insert-file-contents file-name nil)
(set-buffer-file-coding-system coding-system))
(after-find-file nil nil t))
(t (user-error "Recover-file canceled")))))
(eval-after-load 'files
`(progn
(defalias 'save-some-buffers 'my:save-some-buffers)
(defalias 'recover-file 'my:recover-file)))
recover-file의 경우는 각 파일의 저장 시간을 보는 것도 중요하므로 파일의 변경점을 보여 주는 것과 동시에 원래 recover-file의 표시 내용처럼 파일의 directory listing 결과도 함께 보여 줄 필요가 있다. 따라서 alias를 정의 한 이후, 자동 저장이 존재하는 버퍼에서 M-x recover-this-file RET를 실행하면 아래의 이미지와 같이, 왼쪽에는 해당 버퍼, 오른쪽에는 변경점, 그리고 아래쪽에는 원래 보여주던 directory listing 결과를 보여준다.
이제 복구 할 건지 말 건지를 파일을 열어서 일일히 확인할 필요 없이 복구 프롬프트에서 간단하게 결정할 수 있다.
'Emacs' 카테고리의 다른 글
| Emacs에서 End of Line 타입 변경을 간편하게! (0) | 2016.05.11 |
|---|---|
| Emacs 읽기 전용 버퍼에서 모드 키 조합 없이 이동하기 (0) | 2016.04.07 |
| Emacs의 버퍼 변경 알림 프롬프트에서 변경 내용 표시하기 (0) | 2016.04.01 |
| Emacs spell checker (0) | 2016.03.11 |
| Emacs의 ansi color 설정 (0) | 2016.03.07 |