Version Control (C-x v)

Scimax brings the Emacs VC keymap to VS Code. The centerpiece is C-x v v (scimax.vc.nextAction), the counterpart of Emacs vc-next-action: it looks at the current file's git state and does the next logical thing (register it, commit it, mark a conflict resolved, or push/pull the branch). The rest of the C-x v prefix covers the scimax additions to vc-prefix-map (push, pull, magit-status) and the standard Emacs bindings for diff, log, revert, and blame.

The commands work on the file in the active editor. From a right-click menu they work on the file you clicked. In dired, and in the Explorer with several files selected, they work on all of them at once (see Several Files at Once).

C-x v v: Next Action

File stateWhat C-x v v does
Not in a git repositoryOffers to create a repository (in the workspace folder) and register it
UntrackedRegisters the file (git add)
Modified, added, or stagedOpens a *vc-log* buffer for the commit message, then commits this file
Modified, others stagedOpens a log buffer that commits all staged changes plus this file
Up to date, others stagedOpens a log buffer that commits all staged changes
Conflicted, markers remainWarns you; offers the first conflict (smerge mode) or the merge editor
Conflicted, markers goneMarks the file resolved (git add)
Edited during a mergeOpens a log buffer to conclude the merge (commits everything staged)
Ignored by .gitignoreTells you the file is ignored
Up to date, branch aheadOffers to push
Up to date, branch behindOffers to pull
Up to date, branch divergedOffers to sync (pull then push)
Up to date, in syncReports that the file is up to date

Unsaved changes are saved before the file's state is checked.

When nothing else is staged, a commit includes only the current file, as in Emacs: it runs git commit -- <file>, so the file's working-tree content is committed even if you never staged it.

When other files are already staged (for example, you staged several files in the Source Control view), C-x v v commits them all in one commit: it stages the current file's changes, if any, and commits the whole index. The log buffer lists every file that will be committed. Edited files you did not stage are left out. Git does not allow single-file commits while a merge is in progress, so in that case the log buffer commits all staged changes to conclude the merge.

Ahead/behind counts come from the last fetch. Run Fetch from the menu (C-x v m f) to refresh them.

The vc-log Buffer

When a commit is next, C-x v v opens a new git-commit buffer. Write the message on the first lines; the # lines below show the file, the repository, and a diffstat, and are left out of the message.

KeyCommandAction
C-c C-cscimax.vc.logFinishCommit with the buffer text as the message
C-c C-kscimax.vc.logCancelClose the buffer without committing

If the commit fails (for example, a pre-commit hook rejects it) the error is shown and the buffer stays open so you can try again. An empty message commits nothing.

Several Files at Once

Like Emacs VC, the C-x v commands accept a fileset. The files come from, in order:

  1. Several files (or a folder) selected in the Explorer, then right-click → Version Control

  2. The marked files in dired (C-x d), or the file at point when nothing is marked; in dired, . stands for the whole directory

  3. The marked files in the changed-files list (C-x v d, below)

  4. Otherwise, the file in the active editor

A single file goes through the one-file commands described above. A set of files, or a directory, is handled like this:

CommandOn a fileset
C-x v vNext action for the whole set; the changed files must all be in one state
C-x v =One multi-file diff editor: HEAD against the working files
C-x v lCommits touching any of the files; the picked one opens as a multi-file diff
C-x v uDiscard every change in the set, after one confirmation listing the files
C-x v iA checklist of .gitignore patterns, one per file (a folder gets a trailing /)
Stage/UnstageStage the set, or unstage it when everything in it is already staged

What C-x v v does on a fileset (unchanged files in the set are left out):

Changed files areWhat C-x v v does
All untrackedRegisters them all (git add)
All edited (or staged)One *vc-log* buffer that commits exactly those files (git commit -- …)
All conflicted, resolvedMarks them resolved
Conflicted, markers remainNames the files that still have markers; offers the first conflict
In different statesRefuses, naming the files in each state
None (all up to date)Commits anything already staged, else offers push or pull as for one file

A fileset commit leaves other staged files staged. A staged rename is committed as one change (the new and old paths together). The files must all be in one repository.

Hunk-by-hunk staging is not duplicated here: use the Source Control view (C-x v t without edamagit) or edamagit.

C-x v d: Changed Files (vc-dir)

C-x v d (scimax.vc.dir) opens a dired buffer that lists every changed file in the repository (of the active file, the dired directory, or the workspace folder), with its git status, like Emacs vc-dir. The status bar shows the branch, its upstream, and how many commits there are to push or pull. Deleted files are listed struck through. Untracked directories are listed as one entry; ignored files are not listed.

All the dired marking keys work (m, u, U, t, % m, * e, * ?, * c, M), and so do the C-x v commands. Single keys act on the marked files, or the file at point:

KeyAction
vNext action (same as C-x v v)
=Diff
lLog
sStage, or unstage when already staged
GIgnore
PPush
+Pull
MMark every file in the same state as the file at point
RETOpen the file
gRefresh
^Leave vc-dir: dired at the repository root
qClose

A typical round: C-x v d, * e to mark the edited files, = to look over the changes, u on any you want to leave out, then v to commit the rest.

Keybindings

KeybindingCommandEmacs equivalentDescription
C-x v vscimax.vc.nextActionvc-next-actionDo the next logical VC step on the file
C-x v pscimax.vc.pushvc-git-push (scimax)Push the current branch
C-x v Pscimax.vc.pullvc-git-pull (scimax)Pull the current branch
C-x v tscimax.vc.statusmagit-statusedamagit status if installed, else Source Control
C-x v dscimax.vc.dirvc-dirList the repository's changed files in dired
C-x v =scimax.vc.diffvc-diffDiff the file against HEAD
C-x v lscimax.vc.logvc-print-logPick a commit from the file's history and diff it
C-x v uscimax.vc.revertvc-revertDiscard the file's changes (asks first)
C-x v gscimax.vc.annotatevc-annotateToggle line-by-line blame
C-x v iscimax.vc.ignorevc-ignoreAdd the file to .gitignore
C-x v mscimax.hydra.vcscimax VC hydraOpen the Version Control menu

C-x v P is Ctrl+X V Shift+P. In Emacs, vc-ignore is on C-x v G and C-x v i is vc-register; scimax uses C-x v i for ignore because C-x v v already registers untracked files.

C-x v i: Ignore a File

C-x v i (scimax.vc.ignore) adds the current file to the .gitignore at the repository root. It prompts with the file's path relative to the root, anchored with a leading / (for example /meeting-notes/deck.pptx); edit it into a broader pattern such as *.pptx before pressing Enter. The pattern is not added twice.

.gitignore only affects untracked files. If the file is staged as new, it is unstaged. If it is already committed, scimax offers Stop Tracking, which runs git rm --cached so the file leaves the repository (in your next commit) but stays on disk.

Resolving Merge Conflicts (smerge mode)

After a merge or pull with conflicts, git leaves conflict blocks in the file:

<<<<<<< HEAD
The rate constant is 0.12 1/s.
=======
The rate constant is 0.15 1/s.
>>>>>>> feature

The part above ======= is mine (the branch you are on). The part below is theirs (the branch being merged in). With merge.conflictStyle set to diff3 or zdiff3, a ||||||| section also holds the base: the common ancestor of both sides.

Like Emacs smerge-mode, smerge mode turns on in a file that has conflict blocks and that git lists as conflicted (unmerged), after a merge, pull, rebase, cherry-pick or git stash pop. A file that only shows a conflict as an example, like this one, is left alone. The status bar shows smerge 2/5 (the conflict you are at, out of the total); click it for the Conflicts menu. It turns off when the last block is gone. The commands also work from the Command Palette or the Conflicts menu in any file with conflict blocks.

Single keys at a marker line

With the cursor at the start of a marker line (<<<<<<<, |||||||, ======= or >>>>>>>), single keys act on that conflict, like speed commands at a heading. Everywhere else keys type as usual, so you can still edit a conflict by hand.

KeyCommandAction
nscimax.smerge.nextGo to the next conflict
pscimax.smerge.previousGo to the previous conflict
mscimax.smerge.keepMineKeep mine
tscimax.smerge.keepTheirsKeep theirs
bscimax.smerge.keepBothKeep both, mine first
Bscimax.smerge.keepBaseKeep the base (diff3 conflicts only)
Mscimax.smerge.keepAllMineKeep mine in every conflict in the file
Tscimax.smerge.keepAllTheirsKeep theirs in every conflict in the file
RETscimax.smerge.keepCurrentKeep the side of this marker (<<<<<<< mine, >>>>>>> theirs)
=scimax.smerge.compareDiff mine against theirs in the diff editor
escimax.smerge.mergeEditorOpen the file in VS Code's 3-way merge editor
?scimax.smerge.menuOpen the Conflicts menu

After m, t, b, B or RET the cursor moves to the next conflict's <<<<<<< line, so a file is resolved with a run of keys such as m t t b. Messages name each side with the label on its marker, e.g. "Kept theirs (feature)". During a rebase git swaps the sides: mine (HEAD) is the branch you are rebasing onto, and theirs is your commit being replayed.

C-c ^ from anywhere in the file

The same final keys work after C-c ^ anywhere in a file with conflicts, as in Emacs (C-c ^ n, C-c ^ m, C-c ^ RET, and so on). There, C-c ^ RET keeps the side the cursor is in. In org tables C-c ^ sorts by column; in a file with conflicts it is the smerge prefix instead.

When the last conflict is resolved

When no conflict blocks are left and git still lists the file as conflicted, scimax offers Mark Resolved: it saves the file and runs git add. Then C-x v v commits to finish the merge. C-x v v on a file that still has conflict blocks offers Go to First Conflict.

Conflicts menu (scimax.conflicts)

C-x v m m (or ? at a marker line) opens the Conflicts menu. It has the same keys and stays open, so you can press n, m, t, … in a row. Escape goes back to the Version Control menu, and again closes it.

Right-Click Menus

A Version Control submenu appears when you right-click a file tab, a file or folder in the Explorer, or inside an editor. It has Next Action, Diff, File History, Blame, Ignore, Stage / Unstage, Revert, Push, Pull, Status, and VC Directory, acting on the file you clicked, or on every selected file when several are selected in the Explorer.

Version Control Menu (scimax.vc)

Open with C-x v m, or press v in the main menu (C-c m). It mirrors the scimax-version-control hydra from Emacs scimax.

Version Control

File:
  [n] Next Action  - Register, commit, resolve, or sync this file
  [s] Stage        - Stage the file (or the dired marks); unstage if already staged
  [d] Diff         - Diff the file against HEAD
  [l] Log          - History of the current file
  [r] Revert       - Discard changes to the file
  [g] Blame        - Toggle line-by-line blame
  [m] Merge conflicts - Resolve conflict blocks in this file (smerge)
  [i] Ignore       - Add the file to .gitignore

Repository:
  [c] Commit       - Commit staged changes (Source Control)
  [p] Push         - Push the current branch
  [u] Pull         - Pull the current branch
  [f] Fetch        - Fetch from remotes
  [b] Branch       - Create a branch
  [k] Checkout     - Switch branches
  [v] Status       - Repository status (edamagit or Source Control)
  [D] Changed files - The repository's changed files in dired (vc-dir)

How It Works

  • State detection, single-file commits, revert, and file history run the git CLI directly with argument arrays (no shell).

  • C-x v = saves the file, then opens HEAD and the working file side by side in VS Code's diff editor. An added or deleted file is shown as a patch instead. When there is nothing to diff it says so: "No changes between HEAD and notes.org", or that the file is untracked or ignored.

  • A fileset diff or log opens VS Code's multi-file diff editor (vscode.changes); without the Git extension it falls back to one patch.

  • Push, pull, sync, merge editor, and blame are handed to VS Code's built-in Git extension, which handles credentials, progress, and the Source Control view.

  • The Git extension does not open a repository that sits above your workspace folder (when git.openRepositoryInParentFolders is at its default, it asks first), and its commands then report "There are no available repositories". Before handing off, scimax finds the file's repository and opens it in the Git extension, so C-x v p and C-x v t (the Source Control view and edamagit only show repositories it has opened), the menu's Stage, Commit, Fetch, Branch, and Checkout, and the others work from any folder inside a repository.

  • For a magit-like status buffer, install the edamagit extension (kahole.magit); C-x v t uses it automatically.