git-annex/doc/todo/direct_mode_guard.mdwn
Joey Hess 3802f2f270 work around lack of receive.denyCurrentBranch in direct mode
Now that direct mode sets core.bare=true, git's normal prohibition about
pushing into the currently checked out branch doesn't work.

A simple fix for this would be an update hook which blocks the pushes..
but git hooks must be executable, and git-annex needs to be usable on eg,
FAT, which lacks x bits.

Instead, enabling direct mode switches the branch (eg master) to a special
purpose branch (eg annex/direct/master). This branch is not pushed when
syncing; instead any changes that git annex sync commits get written to
master, and it's pushed (along with synced/master) to the remote.

Note that initialization has been changed to always call setDirect,
even if it's just setDirect False for indirect mode. This is needed because
if the user has just cloned a direct mode repo, that nothing has synced
with before, it may have no master branch, and only a annex/direct/master.
Resulting in that branch being checked out locally too. Calling setDirect False
for indirect mode moves back out of this branch, to a new master branch,
and ensures that a manual "git push" doesn't push changes directly to
the annex/direct/master of the remote. (It's possible that the user
makes a commit w/o using git-annex and pushes it, but nothing I can do
about that really.)

This commit was sponsored by Jonathan Harrington.
2013-11-05 21:08:31 -04:00

105 lines
4.2 KiB
Markdown

Currently [[/direct_mode]] allows the user to point many normally safe
git commands at his foot and pull the trigger. At LCA2013, a git-annex
user suggested modifying direct mode to make this impossible.
One way to do it would be to move the .git directory. Instead, make there
be a .git-annex directory in direct mode repositories. git-annex would know
how to use it, and would be extended to support all known safe git
commands, passing parameters through, and in some cases verifying them.
So, for example, `git annex commit` would run `git commit --git-dir=.git-annex`
However, `git annex commit -a` would refuse to run, or even do something
intelligent that does not involve staging every direct mode file.
----
One source of problems here is that there is some overlap between git-annex
and git commands. Ie, `git annex add` cannot be a passthrough for `git
add`. The git wrapper could instead be another program, or it could be
something like `git annex git add`
--[[Joey]]
----
Or, no git wrapper could be provided. Limit the commands to only git-annex
commands. This should be all that is needed to manage a direct mode
repository simply, and if the user is doing something complicated that
needs git access, they can set `GIT_DIR=.git-annex` and be careful not to
shoot off their foot. (Or can just switch to indirect mode!)
This wins on simplicity, and if it's the wrong choice a git wrapper
can be added later. --[[Joey]]
---
Implementation: Pretty simple really. Already did the hard lifting to
support `GIT_DIR`, so only need to override the default git directory
in direct mode when that's not set to `.git-annex`.
A few things hardcode ".git", including Assistant.Threads.Watcher.ignored
and `Seek.withPathContents`, and parts of `Git.Construct`.
---
Transition: git-annex should detect when it's in a direct mode repository
with a .git directory and no .git-annex directory, and transparently
do the move to transition to the new scheme. (And remember that `git annex
indirect` needs to move it back.)
# alternative approach: move index
Rather than moving .git, maybe move .git/index?
This would cause git to think that all files in the tree were deleted.
So git commit -a would make a commit that removes them from git history.
But, the files in the work tree are not touched by this.
Also, git checkout, git merge, and other things that manipulate the work
tree refuse to do anything if they'd change a file that they think is
untracked.
Hmm, this does't solve the user accidentially running git add on an annexed
file; the whole file still gets added.
# alternative approach: fake bare repo
Set core.bare to true. This prevents all work tree operations,
so prevents any foot shooting. It still lets the user run commands like
git log, even on files in the tree, and git fetch, and push, and git
config, etc.
Even better, it integrates with other tools, like `mr`, so they know
it's a git repo.
This seems really promising. But of course, git-annex has its own set of
behaviors in a bare repo, so will need to recognise that this repo is not
really bare, and avoid them.
> [[done]]!! --[[Joey]]
(Git may also have some bare repo behaviors that are unwanted. One example
is that git allows pushes to the current branch in a bare repo,
even when `receive.denyCurrentBranch` is set.)
> This is indeed a problem. Indeed, `git annex sync` successfully
> pushes changes to the master branch of a fake bare direct mode repo.
>
> And then, syncing in the repo that was pushed to causes the changes
> that were pushed to the master branch to get reverted! This happens
> because sync commits; commit sees that files are staged in index
> differing from the (pushed) master, and commits the "changes"
> which revert it.
>
> Could fix this using an update hook, to reject the updated of the master
> branch. However, won't work on crippled filesystems! (No +x bit)
>
> Could make git annex sync detect this. It could reset the master
> branch to the last one committed, before committing. Seems very racy
> and hard to get right!
>
> Could make direct mode operate on a different branch, like
> `annex/direct/master` rather than `master`. Avoid pushing to that
> branch (`git annex sync` can map back from it to `master` and push there
> instead). A bit clumsy, but works.