;;; evc.el --- place /etc contents under version control

;; Author: Thien-Thi Nguyen <ttn@gnu.org>
;; Version: 1.0
;; Keywords: sysadmin, devuan, automation

;; This file is NOT part of GNU Emacs.

;; This program is free software; you can redistribute it and/or
;; modify it under the terms of the GNU General Public License as
;; published by the Free Software Foundation; either version 3 of
;; the License, or (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <http://www.gnu.org/licenses/>.

;;; Commentary:

;; Like many people, I like to keep files under /etc under version control,
;; Git specifically.  Rather than "git add" all the files in one fell swoop,
;; I prefer to group them into understandable package-related chunks if
;; possible.  Thus, evc.el, which helps automate a portion of this task.
;; (It doesn't do everything.)
;;
;; To play, first figure out which packages install files in /etc and save
;; this to a buffer, one package per line.  You can start with the command:
;;
;;  find /etc -type f -o type l | xargs dpkg -S | sed 's/:.*//' | sort | uniq
;;
;; cleaning up the output as needed (removing the error messages, basically).
;;
;; Next, seed a valid ChangeLog file in /etc, something like:
;;
;;  2021-11-09  root  <root@abolire>
;;
;;          Initialize Git repo for /etc
;;
;;          * .git: New (initialize git repo).
;;          * .gitignore: New.
;;
;; You can do this with ‘add-change-log-entry’, for instance.
;;
;; Lastly, place point on the name of a package and invoke ‘evc-package’.
;; (Repeat as necessary.)
;;
;; In addition to the obvious keyboard-macro potential of the exercise, I
;; like to order addition of packages from more "internal" or "low-level" to
;; more "external" or "high-level", e.g., kernel stuff before X11 stuff, but
;; YMMV.  M-x praise-emacs!
;;
;; Note that this uses variables ‘user-login-name’ and ‘user-mail-address’,
;; so make sure those are set properly for best results.

;;; Code:

(require 'cl-lib)

(defun evc-line-at-point ()
  "Return the current line as a string."
  (buffer-substring-no-properties
   (line-beginning-position)
   (line-end-position)))

;;;###autoload
(defun evc-package (pkg)
  "Place /etc files from PKG under version control.
If there are no such files, signal error.
As a side-effect, this updates /etc/ChangeLog as well
and finishes with point left in that file."
  (interactive (list (evc-line-at-point)))
  (let ((files (with-temp-buffer
                 (call-process "dpkg-query" nil t nil "-L" pkg)
                 ;; Decruft.
                 (goto-char (point-min))
                 (keep-lines "^/etc")
                 ;; Return a list.
                 (split-string (buffer-string))))
        ;; Work in /etc.
        (default-directory "/etc"))

    ;; Exclude directories.
    (unless (setq files (cl-remove-if #'file-directory-p files))
      (user-error "Nothing under /etc for package ‘%s’" pkg))

    ;; Remove leading "/etc/".
    (setq files (mapcar (lambda (filename)
                          (substring filename 5))
                        files))

    ;; Construct log components.
    (let ((title (format "Add files%s from package ‘%s’"
                         (if (cl-some #'file-symlink-p files)
                             "/symlinks"
                           "")
                         pkg))
          (lines (mapcar (lambda (filename)
                           (format "* %s: New." filename))
                         files)))

      ;; Add files to Git index.
      (apply #'call-process "git" nil nil nil "add" "--" files)

      ;; Commit them.
      (call-process "git" nil nil nil "commit"
                    ;; git-commit(1) sez:
                    ;;  If multiple -m options are given, their values
                    ;;  are concatenated as separate paragraphs.
                    "-m" title
                    "-m" (mapconcat #'identity lines "\n"))

      ;; Update ChangeLog as well.
      (set-buffer (find-file "ChangeLog"))
      (goto-char (point-min))
      (insert (format "%s  %s  <%s>\n\n"
                      (format-time-string "%F")
                      user-login-name
                      user-mail-address)
              "\t" title "\n\n"
              (mapconcat (lambda (line)
                           (concat "\t" line))
                         lines
                         "\n")
              "\n\n")
      (save-buffer))))

(provide 'evc)

;;; evc.el ends here
