Skip to content

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

About

Project-level Overleaf integration for Emacs

Topics

Resources

Stars

6 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages