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.
|