summaryrefslogtreecommitdiff
path: root/modules/reconcile-open-repos.el
blob: 648de2228f9d577df9a7354f44dbdc1e267ebb40 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
;;; reconcile-open-repos.el --- reconcile open repos -*- lexical-binding: t; coding: utf-8; -*-
;; author: Craig Jennings <c@cjennings.net>
;;
;;; Commentary:
;;
;; Git repository reconciliation workflow for multiple projects. Ensures all git
;; repositories in your projects/ and code/ directories are synchronized with
;; remotes and have no uncommitted work at the start and end of work sessions.
;; The workflow iterates through all git repositories in projects-dir and
;; code-dir, skips local-only repos and http/https remotes (reference clones),
;; silently pulls latest changes for clean repos, and for dirty repos stashes
;; changes, pulls, pops stash, and opens Magit for review. Also checks org-dir
;; and user-emacs-directory individually.
;;
;; Main function: cj/check-for-open-work (bound to M-P)
;;
;; Dependencies: Requires projects-dir, code-dir, and org-dir to be defined in
;; init.el. Uses Magit for manual intervention when repositories have uncommitted
;; changes.

;;; Code:

;; Forward declarations for variables defined in init.el
(eval-when-compile
  (defvar projects-dir)
  (defvar code-dir)
  (defvar org-dir))

;; Forward declaration for magit
(declare-function magit-status "magit" (&optional directory cache))

;; -------------------------- Reconcile Git Directory --------------------------

(defun cj/reconcile-git-directory (directory)
  "Reconcile unopened work in a git project DIRECTORY.
Skips local-only repos and http/https remotes.  For clean repos, silently pulls
latest changes.  For dirty repos, stashes changes, pulls, pops stash, and opens
Magit for review."
  (message "checking: %s" directory)
  (let ((default-directory directory))
    ;; Check for the presence of the .git directory
    (if (file-directory-p (expand-file-name ".git" directory))
        (progn
          (let ((remote-url (string-trim (shell-command-to-string "git config --get remote.origin.url"))))

            ;; skip local git repos, or remote URLs that are http or https,
            ;; these are typically cloned for reference only
            (unless (or (string-empty-p remote-url)
                        (string-match-p "^\\(http\\|https\\)://" remote-url))

              ;; if git directory is clean, pulling generates no errors
              (if (string-empty-p (shell-command-to-string "git status --porcelain"))
                  (progn
                    (let ((pull-result (shell-command "git pull --quiet")))
                      (unless (= pull-result 0)
                        (message "Warning: git pull failed for %s (exit code: %d)" directory pull-result))))

                ;; if directory not clean, pull latest changes and display Magit for manual intervention
                (progn
                  (message "%s contains uncommitted work" directory)
                  (let ((stash-result (shell-command "git stash --quiet")))
                    (if (= stash-result 0)
                        (progn
                          (let ((pull-result (shell-command "git pull --quiet")))
                            (if (= pull-result 0)
                                (let ((stash-pop-result (shell-command "git stash pop --quiet")))
                                  (unless (= stash-pop-result 0)
                                    (message "Warning: git stash pop failed for %s - opening Magit" directory)))
                              (message "Warning: git pull failed for %s - opening Magit" directory)))
                          (magit-status directory))
                      (message "Warning: git stash failed for %s - opening Magit" directory)
                      (magit-status directory)))))))))))

;; ---------------------------- Check For Open Work ----------------------------

;;;###autoload
(defun cj/check-for-open-work ()
  "Check all project directories for open work."
  (interactive)
  ;; these are constants defined in init.el
  ;; children of these directories will be checked
  (dolist (base-dir (list projects-dir code-dir))
    (when (and (boundp 'base-dir) base-dir (file-directory-p base-dir))
      (dolist (child-dir (directory-files base-dir t "^[^.]+$" 'nosort))
        (when (file-directory-p child-dir)
          (cj/reconcile-git-directory child-dir)))))

  ;; check these directories individually
  (when (and (boundp 'org-dir) org-dir (file-directory-p org-dir))
    (cj/reconcile-git-directory org-dir))
  (when (and (boundp 'user-emacs-directory) user-emacs-directory (file-directory-p user-emacs-directory))
    (cj/reconcile-git-directory user-emacs-directory))

  ;; communicate when finished.
  (message "Complete. All repositories checked and updated"))

;;;###autoload (keymap-global-set "M-P" #'cj/check-for-open-work)

(provide 'reconcile-open-repos)
;;; reconcile-open-repos.el ends here.