Folders and files
| Name | Name | Last commit date | ||
|---|---|---|---|---|
Repository files navigation
:PROPERTIES: :ID: 9d694771-66a1-46dc-873a-858289333e3d :END: #+title: git-overleaf #+subtitle: Git-based Overleaf project synchronization for Emacs [[https://github.com/Jamie-Cui/git-overleaf.el/actions/workflows/test.yml][https://github.com/Jamie-Cui/git-overleaf.el/actions/workflows/test.yml/badge.svg]] [[https://github.com/Jamie-Cui/git-overleaf.el/actions/workflows/coverage.yml][https://github.com/Jamie-Cui/git-overleaf.el/actions/workflows/coverage.yml/badge.svg]] [[https://github.com/Jamie-Cui/git-overleaf.el/actions/workflows/melpazoid.yml][https://github.com/Jamie-Cui/git-overleaf.el/actions/workflows/melpazoid.yml/badge.svg]] [[https://melpa.org/#/git-overleaf][https://melpa.org/packages/git-overleaf-badge.svg]] Chinese README: [[file:README.zh.org][README.zh.org]] ~git-overleaf~ integrates Emacs with Overleaf at the project level. It clones an Overleaf project into a local Git repository, records the Overleaf project metadata in that repository, and synchronizes complete project snapshots between local Git history and Overleaf. This package is designed for users who prefer local LaTeX editing, ordinary Git commits, and Git-based conflict resolution while still collaborating with people who use the Overleaf web editor. ~git-overleaf~ is explicitly inspired by [[https://github.com/vale981/overleaf.el][overleaf.el]] and builds on that idea with a project-wide Git snapshot workflow. * Features - Clone a complete Overleaf project into a local Git repository. - Bind an existing Git repository to an Overleaf project. - Push local Git snapshots to Overleaf and pull remote Overleaf changes back into the current branch. - Detect local/remote divergence by comparing the last synchronized commit, local ~HEAD~, and the latest Overleaf snapshot. - Resolve conflicts with normal Git merge tools, including Magit. - Preserve existing Overleaf text document ids when possible by updating documents through Overleaf's real-time text OT channel. - Store authentication cookies in ~auth-source~, a plain cookie file, or the current Emacs session. - Optionally run network, unzip, and Git work in background Emacs threads. - Provide optional ~magit-status~ integration for Overleaf-managed repositories. ~git-overleaf~ does not provide the single-buffer live editing workflow of [[https://github.com/vale981/overleaf.el][overleaf.el]]. New and existing configurations should load ~git-overleaf~ directly. * Requirements ~git-overleaf~ requires Emacs 29.4 or later and the following Elisp packages: - =websocket= 1.15 or later - =webdriver= 0.1 or later - =magit-section= 4.5 or later - =transient= 0.7.2 or later Project synchronization also requires these external executables: - =git= - =curl= - =unzip= The default authentication backend uses Firefox through Selenium webdriver and requires =geckodriver=. The Firefox cookie import backend does not require =geckodriver=, but it requires an Emacs build with SQLite support. The optional =magit-status= integration additionally requires Magit at runtime. * Installation ** MELPA ~git-overleaf~ is available from MELPA. The recommended installation path is Emacs' built-in package manager. If MELPA is not already configured, add it to ~package-archives~: #+begin_src elisp (require 'package) (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t) (package-initialize) #+end_src Then refresh package contents and install ~git-overleaf~: #+begin_example M-x package-refresh-contents RET M-x package-install RET git-overleaf RET #+end_example With =use-package=: #+begin_src elisp (use-package git-overleaf :ensure t) #+end_src For a self-hosted Overleaf instance, set the server URL before using the interactive commands: #+begin_src elisp (setopt git-overleaf-url "https://latex.example.edu") #+end_src ** Development version To install unreleased changes from the Git repository, use =use-package= with VC support: #+begin_src elisp (use-package git-overleaf :vc (:url "https://github.com/Jamie-Cui/git-overleaf" :rev "main")) #+end_src Without =use-package=, use Emacs' VC package installer directly: #+begin_example M-x package-vc-install RET https://github.com/Jamie-Cui/git-overleaf.git RET #+end_example ** Manual checkout If using a local checkout, install the Elisp dependencies separately and add this repository to ~load-path~: #+begin_src elisp (add-to-list 'load-path "/path/to/git-overleaf") (require 'git-overleaf) #+end_src The package feature, interactive commands, and customization variables use the ~git-overleaf-~ prefix. * Quick Start 1. Authenticate with Overleaf: #+begin_example M-x git-overleaf-authenticate #+end_example 2. Clone an Overleaf project: #+begin_example M-x git-overleaf-clone #+end_example 3. Edit locally and commit changes with Git. 4. Push local commits to Overleaf: #+begin_example M-x git-overleaf-push #+end_example 5. Pull remote Overleaf changes when collaborators edit in the web editor: #+begin_example M-x git-overleaf-pull #+end_example If the project already exists as a local Git repository, run ~git-overleaf-init~ instead of ~git-overleaf-clone~ to bind it to an Overleaf project. * Authentication Valid Overleaf session cookies are required before cloning, fetching, pushing, or pulling. The primary command is: #+begin_example M-x git-overleaf-authenticate #+end_example If ~git-overleaf-clone~, ~git-overleaf-init~, ~git-overleaf-fetch~, ~git-overleaf-push~, or ~git-overleaf-pull~ starts without valid cookies, it prompts in the minibuffer to run ~git-overleaf-authenticate~ first. ** Webdriver backend The default backend is ~webdriver~: #+begin_src elisp (setopt git-overleaf-auth-backend 'webdriver) #+end_src It starts =geckodriver= on a free local port, opens Firefox, and asks you to log in to Overleaf. A stale =geckodriver= process listening on the default port =4444= should not prevent re-authentication. By default, authenticated cookies are stored with Emacs ~auth-source~. The package uses the first file entry in ~auth-sources~, including GPG encrypted files, and falls back to =~/.authinfo.gpg=. When updating an existing encrypted file it preserves that file's recipients. It manages one entry per Overleaf host using =login git-overleaf= and =port git-overleaf-cookie=. Re-authentication replaces that managed entry instead of appending stale copies. ** Firefox cookie import If Firefox is already logged in to Overleaf, cookies can be imported from the default Firefox profile: #+begin_src elisp (setopt git-overleaf-auth-backend 'firefox-cookies) #+end_src This backend reads =profiles.ini=, imports only cookies for the current ~git-overleaf-url~, and fails with an explicit message if no valid Overleaf session cookie is found. It supports both expiry timestamps in seconds used by older Firefox versions and timestamps in milliseconds used by newer versions. If Overleaf rejects a saved session, project discovery asks you to log in again and rerun ~git-overleaf-authenticate~. To bypass profile discovery: #+begin_src elisp (setopt git-overleaf-firefox-profile "/path/to/firefox/profile") #+end_src ** Cookie storage To use the default encrypted auth-source storage: #+begin_src elisp (setopt auth-sources '("~/.authinfo.gpg") git-overleaf-cookie-storage 'authinfo) #+end_src When the encrypted file does not exist yet, EasyPG prompts for the encryption recipients on the first save. To store cookies in a plain file: #+begin_src elisp (setopt git-overleaf-cookie-storage "~/.git-overleaf-cookies") #+end_src To keep cookies only in memory for the current Emacs session: #+begin_src elisp (setopt git-overleaf-cookie-storage nil) #+end_src Advanced configurations may set ~git-overleaf-cookies~ and ~git-overleaf-save-cookies~ directly. For cookies saved by ~git-overleaf-authenticate~, session expiry is checked from the locally saved session-cookie expiry before any network request. Short-lived analytics cookies are ignored for that check. * Workflow ** Clone a project Run: #+begin_example M-x git-overleaf-clone #+end_example The command prompts for an Overleaf project, downloads the complete project zip, creates a local Git repository, commits the imported snapshot, and stores Overleaf metadata in the repository's local Git config. ** Bind an existing repository Run this command from an existing Git repository: #+begin_example M-x git-overleaf-init #+end_example The command prompts for the remote Overleaf project, stores project metadata in local Git config, and initializes the hidden base snapshot used by later push and pull operations. It does not automatically pull from or push to Overleaf. ** Edit locally After cloning or initializing, use the repository as an ordinary Git working tree: - edit files locally; - stage and commit changes as needed; - run ~git-overleaf-push~ to upload local commits; - run ~git-overleaf-pull~ to incorporate remote Overleaf changes. ~git-overleaf-push~ uploads committed ~HEAD~ only. It never stages or commits files, and staged, unstaged, and untracked working-tree changes are left untouched. It fetches the latest remote snapshot and uploads ~HEAD~ only when the remote state has not diverged. Like ~git pull~, ~git-overleaf-pull~ allows staged, unstaged, and untracked local changes when Git can carry them through the merge without overwriting them. Non-overlapping changes remain in the working tree. If incoming changes would overwrite local changes, Git rejects the merge and leaves the local changes intact. Pull must still run from a normal branch, not a detached ~HEAD~. ** Push to Overleaf Run: #+begin_example M-x git-overleaf-push #+end_example The command compares three states: - the last successfully synchronized Git commit; - the current local ~HEAD~; - the latest project snapshot downloaded from Overleaf. Its behavior is: | State | Result | |-------+--------| | Only local changed | Upload local ~HEAD~ to Overleaf. | | Remote already matches ~HEAD~ | Update the base ref only. | | Only remote changed | Stop and ask you to run ~git-overleaf-pull~. | | Local and remote both changed differently | Stop and ask you to pull first. | With a prefix argument, ~C-u M-x git-overleaf-push~ is the equivalent of ~git push --force~: after confirmation it replaces divergent remote content with local ~HEAD~. The Lisp API accepts FORCE as its second argument. Uploads are multi-step Overleaf API operations. Before the first remote mutation, push records a recovery journal. If an upload is interrupted, run push again to resume when the observed remote is a partial application of the same target. If Overleaf also changed independently, run pull to merge it or explicitly force-push to replace it. For existing remote Overleaf text documents, changed UTF-8 text is updated through Overleaf's real-time ShareJS text OT path. This preserves the remote document id and produces normal Overleaf web history entries. Source metadata is assigned by Overleaf's real-time service rather than supplied by the client. Changed binary files, newly added files, type conflicts, and deleted entries use Overleaf's upload and delete APIs. If a remote document cannot be updated through supported text OT, for example because it is not valid UTF-8, uses Overleaf's newer ~history-ot~ type, or changes again after the snapshot is downloaded, the push fails instead of silently deleting and recreating that document. ** Pull from Overleaf Run: #+begin_example M-x git-overleaf-pull #+end_example The command compares the same three states. Its behavior is: | State | Result | |-------+--------| | Only remote changed | Fast-forward the current branch locally. | | Local already matches the remote snapshot | Update the base ref only. | | No remote changes | Report that nothing needs to be pulled. | | Local and remote both changed differently | Merge the remote snapshot into the current branch. | After a clean merge caused by remote divergence, run ~git-overleaf-push~ to publish the merged result back to Overleaf. Local changes that do not overlap incoming paths are preserved across the pull. If Git rejects the merge because local changes would be overwritten, the remote snapshot is still fetched, but no pending pull is recorded; commit, stash, or otherwise move the overlapping changes and retry. After resolving and committing a pull conflict, normal push completes the sync. If Overleaf changed again in the meantime, run pull again; it merges the newer snapshot instead of rejecting the repository merely because a pending pull exists. ** Reset to the cached Overleaf snapshot Run: #+begin_example M-x git-overleaf-fetch M-x git-overleaf-reset #+end_example Reset never accesses the network; it targets the snapshot already stored in ~refs/git-overleaf/remote~. Without a prefix it performs mixed-reset semantics: HEAD and the index move to the cached snapshot while working tree files are preserved. With ~C-u M-x git-overleaf-reset~, it performs hard-reset semantics and also replaces tracked working-tree files. Untracked and ignored files are preserved in both modes; the command never runs ~git clean~. It creates a safety ref before moving HEAD, updates the base, and clears pending synchronization metadata after success. ** Force-push to Overleaf Run: #+begin_example C-u M-x git-overleaf-push #+end_example Force push uses the same HEAD-only upload path but intentionally replaces remote divergence. It refuses an active or unresolved Git merge. A successful force push clears previous pending synchronization state; an interrupted one retains a resumable push journal. ** Conflict handling When local and remote snapshots diverge, run ~git-overleaf-pull~ first. The command merges the downloaded remote snapshot into the current branch with normal Git merge machinery. If the merge conflicts: - the current branch remains in Git's normal conflicted merge state; - resolve conflicts with Magit or plain Git; - create the merge commit; - run ~git-overleaf-push~ to upload the merged result. To abandon an active pull, run ~git-overleaf-pull-abort~. It runs ~git merge --abort~ and clears pending metadata only after Git succeeds. If the merge was already aborted manually, it clears the stale metadata. It refuses after the merge has been committed because silently rewriting committed history would be unsafe. After aborting, keep local content with a force push, or accept the cached remote snapshot with ~git-overleaf-reset~ (use a prefix for hard mode). Conflict resolution is intentionally handled by Git rather than by ediff. * Synchronization Details The clone, init, push, and pull commands store project metadata in the repository's local Git config. After a repository is bound, later pushes and pulls can run from anywhere inside that repository. Clone and init may register a branchless logical Git remote named =overleaf=. It has no URL, push URL, or refspec; its local config marker lets ordinary Magit remote selection route that name through git-overleaf. The logical remote is optional: every git-overleaf command and explicit Magit =O= action works without it. Register or restore it with ~git-overleaf-register-remote~ only when that routing is useful. The latest successfully observed snapshot is stored in ~refs/git-overleaf/remote~, independently of the synchronized base in ~refs/git-overleaf/base~. Normal remotes such as =origin= are unchanged. Successful push operations maintain a root-level ~.git-overleaf-sync.json~ file on the Overleaf project when ~git-overleaf-sync-metadata-enabled~ is non-nil. The file records the last local Git commit and tree uploaded by this package. It is removed from downloaded snapshots before Git comparisons, so it acts as sync bookkeeping rather than normal project content. It should not be tracked in the local Git repository. Before sync steps that can move the current branch or complete pending sync state, the package creates local safety refs under ~refs/git-overleaf/backups/~ when ~git-overleaf-local-backups-enabled~ is non-nil. These refs keep previous local commits reachable independently of branch movement and reflog expiry. Inspect backup refs with: #+begin_example git show-ref refs/git-overleaf/backups #+end_example Recover one by creating a normal branch from the backup ref: #+begin_example git branch recover-overleaf refs/git-overleaf/backups/... #+end_example Empty directories are not tracked by Git and are therefore not preserved by push or pull. * Asynchronous Operation By default, commands run synchronously. To keep Emacs responsive while long network, unzip, and Git work is running: #+begin_src elisp (setopt git-overleaf-enable-async t) #+end_src Interactive commands still collect minibuffer input and confirmation in the foreground, then run the expensive work in the background. This applies to clone, init, fetch, push, pull, authentication, and Magit remote refreshes. Status, reset, and pull-abort are local operations. To cancel currently tracked background Overleaf operations: #+begin_example M-x git-overleaf-force-stop #+end_example Cancellation interrupts external processes where possible, clears async locks, and drops pending foreground callbacks. Work already completed locally or remotely is not rolled back. * Download and Upload Progress Snapshot downloads echo curl progress in the minibuffer as =Downloading project ... N%= when ~git-overleaf-log-echo~ is non-nil. Push operations report the completed content mutations and the current path as =Uploading content for project ... N% (I/T: ACTION PATH)=. This is operation progress rather than a byte count because one push may mix text OT updates, multipart uploads, folder creation, and deletion. A single slow operation can therefore remain at the same percentage until that remote request completes. After content reaches 100%, a separate message identifies sync-metadata finalization. Progress messages are transient and are not added to the project log or =*Messages*=. ** Proxy environment Downloads are performed by ~git-overleaf-curl-executable~, so proxy configuration comes from Emacs' ~process-environment~, not necessarily from the shell that runs =emacsclient=. If Overleaf downloads are slow in GUI Emacs, check =(getenv "HTTPS_PROXY")= and =(getenv "HTTP_PROXY")= inside Emacs, then copy the needed proxy variables with =setenv= or a tool such as =exec-path-from-shell=. * Magit Integration To display an Overleaf section in =magit-status=, load the optional integration: #+begin_src elisp (with-eval-after-load 'magit (require 'git-overleaf-magit) (git-overleaf-magit-setup)) #+end_src For Overleaf-managed repositories, the section is always present. It shows whether the remote is unchecked, being refreshed, in sync, changed locally, changed remotely, or changed on both sides. Staged, unstaged, untracked, and unmerged worktree changes are noted in the heading while their diffs remain in Magit's standard sections. Local and remote snapshot diffs are collapsed and generated only when expanded. Each =magit-status= refresh may also start a background refresh of the remote Overleaf snapshot. This Magit-specific background refresh is controlled by ~git-overleaf-magit-auto-refresh-remote~ independently of ~git-overleaf-enable-async~. Automatic refresh is internally throttled and shares the repository operation lock with push and pull, so ordinary Magit refreshes neither block nor race an active sync operation. To keep remote refresh manual: #+begin_src elisp (setopt git-overleaf-magit-auto-refresh-remote nil) #+end_src Remote state can still be refreshed with: #+begin_example M-x git-overleaf-magit-refresh-remote #+end_example The standard Magit fetch, pull, and push transients each add an uppercase =O= action. These explicit actions work whether or not a logical remote is registered. Fetch only updates ~refs/git-overleaf/remote~; =-f=/=--force= does not turn fetch into a reset. Pull runs ~git-overleaf-pull~. Push runs ~git-overleaf-push~; Magit's force option reaches the same command with a prefix and asks for confirmation. =--force-with-lease= keeps the normal guarded behavior. Other Git-only transient arguments are rejected. When a logical remote is registered, selecting it from Magit's ordinary fetch action routes the snapshot refresh through git-overleaf. Branches and refspecs are unsupported. Other Git remotes retain Magit's normal behavior. Explicit =O= fetch bypasses the automatic refresh throttle. On the Overleaf section, =G= refreshes the remote and =RET= opens the project in a browser. =C-c C-c= is a command prefix with these keys: | Key | Command | |-----+---------| | =b= | Browse the Overleaf project. | | =f= | Fetch the remote snapshot. | | =g= | Refresh the remote snapshot. | | =l= | Pull from Overleaf. | | =p= | Push to Overleaf. | | =q= | Abort a pending pull. | | =R= | Reset to the cached remote snapshot. | | =r= | Register the logical Overleaf remote. | | =s= | Show cached synchronization status. | | =L= | Show the git-overleaf log. | | =k= | Stop background Overleaf operations. | * Commit Hook To push automatically after commits created from Emacs through Magit or =git-commit=: #+begin_src elisp (with-eval-after-load 'git-commit (add-hook 'git-commit-post-finish-hook (lambda () (git-overleaf-push nil nil t)))) #+end_src The hook only pushes repositories that already contain Overleaf project metadata. With synchronous commands, this hook blocks Emacs while the push runs. Enable ~git-overleaf-enable-async~ for background hook-driven pushes. * Commands | Command | Purpose | |---------+---------| | ~git-overleaf-authenticate~ | Authenticate and store Overleaf cookies. | | ~git-overleaf-clone~ | Clone an Overleaf project into a new local Git repository. | | ~git-overleaf-init~ | Bind the current Git repository to an Overleaf project. | | ~git-overleaf-register-remote~ | Register or migrate the branchless logical Overleaf remote. | | ~git-overleaf-status~ | Show cached sync, pending, merge, and worktree state. | | ~git-overleaf-fetch~ | Refresh only the latest Overleaf snapshot ref. | | ~git-overleaf-push~ | Upload local ~HEAD~ to Overleaf when the remote has not diverged. | | ~git-overleaf-pull~ | Pull and merge the latest Overleaf snapshot. | | ~git-overleaf-pull-abort~ | Abort an active pending pull or clear stale pull metadata. | | ~git-overleaf-reset~ | Mixed-reset to the cached remote snapshot; prefix selects hard mode. | | ~git-overleaf-browse-remote~ | Open the bound Overleaf project in a browser. | | ~git-overleaf-force-stop~ | Cancel tracked background Overleaf operations. | | ~git-overleaf-log~ | Display the global log buffer. | | ~git-overleaf-log-clear~ | Clear the global log buffer. | To bind the provided command map: #+begin_src elisp (global-set-key (kbd "C-c o") git-overleaf-command-map) #+end_src Default bindings in ~git-overleaf-command-map~: | Key | Command | |-----+---------| | =a= | ~git-overleaf-authenticate~ | | =b= | ~git-overleaf-browse-remote~ | | =c= | ~git-overleaf-clone~ | | =f= | ~git-overleaf-fetch~ | | =k= | ~git-overleaf-force-stop~ | | =l= | ~git-overleaf-pull~ | | =p= | ~git-overleaf-push~ | | =q= | ~git-overleaf-pull-abort~ | | =R= | ~git-overleaf-reset~ | | =r= | ~git-overleaf-register-remote~ | | =s= | ~git-overleaf-status~ | * Customization Important user options include: | Option | Description | |--------+-------------| | ~git-overleaf-url~ | Overleaf server URL. | | ~git-overleaf-auth-backend~ | Authentication backend: ~webdriver~ or ~firefox-cookies~. | | ~git-overleaf-cookie-storage~ | Cookie storage backend: ~authinfo~, a file path, or nil. | | ~git-overleaf-firefox-profile~ | Explicit Firefox profile path for cookie import. | | ~git-overleaf-enable-async~ | Run long interactive operations in background threads. | | ~git-overleaf-remote-name~ | Default branchless logical remote name. | | ~git-overleaf-remote-ref~ | Ref storing the latest observed Overleaf snapshot. | | ~git-overleaf-sync-metadata-enabled~ | Enable the remote sync metadata file. | | ~git-overleaf-sync-metadata-file~ | Name of the reserved remote metadata file. | | ~git-overleaf-local-backups-enabled~ | Create local safety refs during sync operations. | | ~git-overleaf-local-backup-ref-prefix~ | Ref namespace for local safety refs. | | ~git-overleaf-socket-timeout~ | Timeout for websocket project tree fetches. | | ~git-overleaf-git-executable~ | Git executable path. | | ~git-overleaf-curl-executable~ | Curl executable path. | | ~git-overleaf-unzip-executable~ | Unzip executable path. | | ~git-overleaf-debug~ | Enable verbose debug logging. | | ~git-overleaf-log-echo~ | Echo log entries in the minibuffer. | | ~git-overleaf-magit-auto-refresh-remote~ | Automatically refresh remote snapshots from Magit. | Use =M-x customize-group RET git-overleaf RET= to inspect the full customization group. * Logging Overleaf messages, warnings, and debug entries are written to the global ~*git-overleaf-log*~ buffer. Each entry includes a timestamp, log level, and the best available project, repository, or Overleaf URL context, which helps distinguish logs from multiple projects. Verbose debug entries can be enabled with: #+begin_src elisp (setopt git-overleaf-debug t) #+end_src * Security Notes Overleaf cookies grant access to your Overleaf account. Keep auth-source files and custom cookie files private, and do not commit real cookies, project identifiers, ancestor backup files, or debug logs that contain session data. * Development The repository includes a small =Makefile= for local validation: #+begin_example make make test make coverage make help make clean #+end_example If the Emacs executable is not available on =PATH= as =emacs=: #+begin_example make EMACS=/path/to/Emacs #+end_example Equivalent batch validation commands: #+begin_example emacs -Q --batch -L . --eval "(progn (require 'package) (package-initialize))" -f batch-byte-compile git-overleaf.el emacs -Q --batch -L . --eval "(progn (require 'package) (package-initialize))" --eval "(require 'git-overleaf)" #+end_example The automated test suite uses ERT and is designed to run offline: #+begin_example make test #+end_example It covers pure helpers, cookie and HTTP boundary logic, local Git snapshot/state transitions, remote tree diffing with stubbed Overleaf API calls, command dispatch, async state handling, and small Magit integration helpers. It intentionally does not contact Overleaf, start webdriver, or open a browser. For changes that affect the real service boundary, also validate clone, init, push, pull, pending-state recovery, and Git conflict resolution against a real Overleaf session. For authentication changes, validate both webdriver authentication and Firefox cookie import when possible. Coverage is not currently wired into the default build. Emacs includes the built-in =testcover= library. To run the offline ERT suite under coverage instrumentation: #+begin_example make coverage make coverage COVERAGE_MIN=50 #+end_example The target writes a tab-separated summary to =coverage/testcover-summary.tsv=. The optional =COVERAGE_MIN= variable turns the report into a threshold check. GitHub Actions also exposes a manual =workflow_dispatch= trigger that runs =make coverage= and uploads the summary artifact. External packages such as =undercover.el= can report ERT coverage to hosted coverage services, but that is not part of the default CI path. * Acknowledgements ~git-overleaf~ was inspired by [[https://github.com/vale981/overleaf.el][vale981/overleaf.el]], but it uses a project-wide Git snapshot workflow instead of single-buffer live editing. * License ~git-overleaf~ is distributed under the terms of the GNU General Public License, version 3 or later. See [[file:LICENSE][LICENSE]].