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 state | What C-x v v does |
|---|---|
| Not in a git repository | Offers to create a repository (in the workspace folder) and register it |
| Untracked | Registers the file (git add) |
| Modified, added, or staged | Opens a *vc-log* buffer for the commit message, then commits this file |
| Modified, others staged | Opens a log buffer that commits all staged changes plus this file |
| Up to date, others staged | Opens a log buffer that commits all staged changes |
| Conflicted, markers remain | Warns you; offers the first conflict (smerge mode) or the merge editor |
| Conflicted, markers gone | Marks the file resolved (git add) |
| Edited during a merge | Opens a log buffer to conclude the merge (commits everything staged) |
Ignored by .gitignore | Tells you the file is ignored |
| Up to date, branch ahead | Offers to push |
| Up to date, branch behind | Offers to pull |
| Up to date, branch diverged | Offers to sync (pull then push) |
| Up to date, in sync | Reports 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.
| Key | Command | Action |
|---|---|---|
C-c C-c | scimax.vc.logFinish | Commit with the buffer text as the message |
C-c C-k | scimax.vc.logCancel | Close 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:
Several files (or a folder) selected in the Explorer, then right-click → Version Control
The marked files in dired (
C-x d), or the file at point when nothing is marked; in dired,.stands for the whole directoryThe marked files in the changed-files list (
C-x v d, below)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:
| Command | On a fileset |
|---|---|
C-x v v | Next 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 l | Commits touching any of the files; the picked one opens as a multi-file diff |
C-x v u | Discard every change in the set, after one confirmation listing the files |
C-x v i | A checklist of .gitignore patterns, one per file (a folder gets a trailing /) |
| Stage/Unstage | Stage 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 are | What C-x v v does |
|---|---|
| All untracked | Registers them all (git add) |
| All edited (or staged) | One *vc-log* buffer that commits exactly those files (git commit -- …) |
| All conflicted, resolved | Marks them resolved |
| Conflicted, markers remain | Names the files that still have markers; offers the first conflict |
| In different states | Refuses, 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:
| Key | Action |
|---|---|
v | Next action (same as C-x v v) |
= | Diff |
l | Log |
s | Stage, or unstage when already staged |
G | Ignore |
P | Push |
+ | Pull |
M | Mark every file in the same state as the file at point |
RET | Open the file |
g | Refresh |
^ | Leave vc-dir: dired at the repository root |
q | Close |
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
| Keybinding | Command | Emacs equivalent | Description |
|---|---|---|---|
C-x v v | scimax.vc.nextAction | vc-next-action | Do the next logical VC step on the file |
C-x v p | scimax.vc.push | vc-git-push (scimax) | Push the current branch |
C-x v P | scimax.vc.pull | vc-git-pull (scimax) | Pull the current branch |
C-x v t | scimax.vc.status | magit-status | edamagit status if installed, else Source Control |
C-x v d | scimax.vc.dir | vc-dir | List the repository's changed files in dired |
C-x v = | scimax.vc.diff | vc-diff | Diff the file against HEAD |
C-x v l | scimax.vc.log | vc-print-log | Pick a commit from the file's history and diff it |
C-x v u | scimax.vc.revert | vc-revert | Discard the file's changes (asks first) |
C-x v g | scimax.vc.annotate | vc-annotate | Toggle line-by-line blame |
C-x v i | scimax.vc.ignore | vc-ignore | Add the file to .gitignore |
C-x v m | scimax.hydra.vc | scimax VC hydra | Open 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.
| Key | Command | Action |
|---|---|---|
n | scimax.smerge.next | Go to the next conflict |
p | scimax.smerge.previous | Go to the previous conflict |
m | scimax.smerge.keepMine | Keep mine |
t | scimax.smerge.keepTheirs | Keep theirs |
b | scimax.smerge.keepBoth | Keep both, mine first |
B | scimax.smerge.keepBase | Keep the base (diff3 conflicts only) |
M | scimax.smerge.keepAllMine | Keep mine in every conflict in the file |
T | scimax.smerge.keepAllTheirs | Keep theirs in every conflict in the file |
RET | scimax.smerge.keepCurrent | Keep the side of this marker (<<<<<<< mine, >>>>>>> theirs) |
= | scimax.smerge.compare | Diff mine against theirs in the diff editor |
e | scimax.smerge.mergeEditor | Open the file in VS Code's 3-way merge editor |
? | scimax.smerge.menu | Open 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.
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.openRepositoryInParentFoldersis 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, soC-x v pandC-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 tuses it automatically.