diff options
author | Joey Hess <joey@kitenet.net> | 2010-11-10 14:11:19 -0400 |
---|---|---|
committer | Joey Hess <joey@kitenet.net> | 2010-11-10 14:11:19 -0400 |
commit | f5f472e8550ae438b1dc751a18cccf0efbaccd1d (patch) | |
tree | 426ccf3aa6191898d27b3f62933170a698ba89c6 | |
parent | 05ca2bebff521b1fa9b79014b1856b828d897b6d (diff) | |
parent | 16ba23d48de00daccbfb481dd1baca91047f8b3e (diff) |
Merge branch 'checkout'
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | CmdLine.hs | 30 | ||||
-rw-r--r-- | Command/Lock.hs | 50 | ||||
-rw-r--r-- | Command/PreCommit.hs | 40 | ||||
-rw-r--r-- | Command/Unlock.hs | 39 | ||||
-rw-r--r-- | Core.hs | 1 | ||||
-rw-r--r-- | GitRepo.hs | 21 | ||||
-rw-r--r-- | Makefile | 11 | ||||
-rw-r--r-- | debian/changelog | 10 | ||||
-rw-r--r-- | doc/backends.mdwn | 2 | ||||
-rw-r--r-- | doc/git-annex.mdwn | 22 | ||||
-rw-r--r-- | doc/todo/backendSHA1.mdwn | 2 | ||||
-rw-r--r-- | doc/todo/checkout.mdwn | 18 | ||||
-rw-r--r-- | doc/walkthrough.mdwn | 58 | ||||
-rw-r--r-- | test.hs | 3 |
15 files changed, 286 insertions, 22 deletions
diff --git a/.gitignore b/.gitignore index 13deb526a..6956c49dd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ build/* +test git-annex git-annex.1 doc/.ikiwiki diff --git a/CmdLine.hs b/CmdLine.hs index 3823c7247..7e6626573 100644 --- a/CmdLine.hs +++ b/CmdLine.hs @@ -30,10 +30,13 @@ import qualified Command.SetKey import qualified Command.Fix import qualified Command.Init import qualified Command.Fsck +import qualified Command.Unlock +import qualified Command.Lock +import qualified Command.PreCommit subCmds :: [SubCommand] subCmds = - [ SubCommand "add" path (withFilesNotInGit Command.Add.start) + [ SubCommand "add" path (withFilesToAdd Command.Add.start) "add files to annex" , SubCommand "get" path (withFilesInGit Command.Get.start) "make content of annexed files available" @@ -41,12 +44,18 @@ subCmds = "indicate content of files not currently wanted" , SubCommand "move" path (withFilesInGit Command.Move.start) "transfer content of files to/from another repository" + , SubCommand "unlock" path (withFilesInGit Command.Unlock.start) + "unlock files for modification" + , SubCommand "edit" path (withFilesInGit Command.Unlock.start) + "same as unlock" + , SubCommand "lock" path (withFilesInGit Command.Lock.start) + "undo unlock command" , SubCommand "init" desc (withDescription Command.Init.start) "initialize git-annex with repository description" , SubCommand "unannex" path (withFilesInGit Command.Unannex.start) "undo accidential add command" - , SubCommand "pre-commit" path (withFilesToBeCommitted Command.Fix.start) - "fix up symlinks before they are committed" + , SubCommand "pre-commit" path (withFilesToBeCommitted Command.PreCommit.start) + "run by git pre-commit hook" , SubCommand "fromkey" key (withFilesMissing Command.FromKey.start) "adds a file using a specific key" , SubCommand "dropkey" key (withKeys Command.DropKey.start) @@ -106,13 +115,6 @@ usage = usageInfo header options ++ "\nSubcommands:\n" ++ cmddescs {- These functions find appropriate files or other things based on a user's parameters. -} -withFilesNotInGit :: SubCmdSeekBackendFiles -withFilesNotInGit a params = do - repo <- Annex.gitRepo - files <- liftIO $ mapM (Git.notInRepo repo) params - let files' = foldl (++) [] files - pairs <- Backend.chooseBackends files' - return $ map a $ filter (\(f,_) -> notState f) pairs withFilesInGit :: SubCmdSeekStrings withFilesInGit a params = do repo <- Annex.gitRepo @@ -126,6 +128,14 @@ withFilesMissing a params = do missing f = do e <- doesFileExist f return $ not e +withFilesToAdd :: SubCmdSeekBackendFiles +withFilesToAdd a params = do + repo <- Annex.gitRepo + newfiles <- liftIO $ mapM (Git.notInRepo repo) params + unlockedfiles <- liftIO $ mapM (Git.typeChangedFiles repo) params + let files = foldl (++) [] $ newfiles ++ unlockedfiles + pairs <- Backend.chooseBackends files + return $ map a $ filter (\(f,_) -> notState f) pairs withDescription :: SubCmdSeekStrings withDescription a params = return [a $ unwords params] withFilesToBeCommitted :: SubCmdSeekStrings diff --git a/Command/Lock.hs b/Command/Lock.hs new file mode 100644 index 000000000..955749e93 --- /dev/null +++ b/Command/Lock.hs @@ -0,0 +1,50 @@ +{- git-annex command + - + - Copyright 2010 Joey Hess <joey@kitenet.net> + - + - Licensed under the GNU GPL version 3 or higher. + -} + +module Command.Lock where + +import Control.Monad.State (liftIO) +import System.Directory +import System.Posix.Files + +import Types +import Command +import Messages +import qualified Annex +import qualified GitRepo as Git + +{- Undo unlock -} +start :: SubCmdStartString +start file = do + locked <- isLocked file + if locked + then return Nothing + else do + showStart "lock" file + return $ Just $ perform file + +perform :: FilePath -> SubCmdPerform +perform file = do + liftIO $ removeFile file + g <- Annex.gitRepo + -- first reset the file to drop any changes checked into the index + liftIO $ Git.run g ["reset", "-q", "--", file] + -- checkout the symlink + liftIO $ Git.run g ["checkout", "--", file] + return $ Just $ return True -- no cleanup needed + +{- Checks if a file is unlocked for edit. + - + - But, without the symlink to the annex, cannot tell for sure if the + - file was annexed before. So, check if git thinks the file's type has + - changed (from a symlink to a regular file). -} +isLocked :: FilePath -> Annex Bool +isLocked file = do + g <- Annex.gitRepo + typechanged <- liftIO $ Git.typeChangedFiles g file + s <- liftIO $ getSymbolicLinkStatus file + return $ (not $ elem file typechanged) || isSymbolicLink s diff --git a/Command/PreCommit.hs b/Command/PreCommit.hs new file mode 100644 index 000000000..cd6ce6f08 --- /dev/null +++ b/Command/PreCommit.hs @@ -0,0 +1,40 @@ +{- git-annex command + - + - Copyright 2010 Joey Hess <joey@kitenet.net> + - + - Licensed under the GNU GPL version 3 or higher. + -} + +module Command.PreCommit where + +import Control.Monad.State (liftIO) +import Control.Monad (when, unless) + +import Command +import qualified Annex +import qualified Backend +import qualified GitRepo as Git +import qualified Command.Fix +import qualified Command.Lock +import qualified Command.Add + +{- Run by git pre-commit hook. -} +start :: SubCmdStartString +start file = do + -- If a file is unlocked for edit, add its new content to the + -- annex, -} + locked <- Command.Lock.isLocked file + when (not locked) $ do + pairs <- Backend.chooseBackends [file] + ok <- doSubCmd $ Command.Add.start $ pairs !! 0 + unless (ok) $ do + error $ "failed to add " ++ file ++ "; canceling commit" + -- git commit will have staged the file's content; + -- drop that and stage the symlink + g <- Annex.gitRepo + liftIO $ Git.run g ["reset", "-q", "--", file] + Annex.queueRun + + -- Fix symlinks as they are committed, this ensures the + -- relative links are not broken when moved around. + Command.Fix.start file diff --git a/Command/Unlock.hs b/Command/Unlock.hs new file mode 100644 index 000000000..de21988de --- /dev/null +++ b/Command/Unlock.hs @@ -0,0 +1,39 @@ +{- git-annex command + - + - Copyright 2010 Joey Hess <joey@kitenet.net> + - + - Licensed under the GNU GPL version 3 or higher. + -} + +module Command.Unlock where + +import Control.Monad.State (liftIO) +import System.Directory + +import Command +import qualified Annex +import Types +import Messages +import Locations +import Utility +import Core + +{- The unlock subcommand replaces the symlink with a copy of the file's + - content. -} +start :: SubCmdStartString +start file = isAnnexed file $ \(key, _) -> do + showStart "unlock" file + return $ Just $ perform file key + +perform :: FilePath -> Key -> SubCmdPerform +perform dest key = do + g <- Annex.gitRepo + let src = annexLocation g key + liftIO $ removeFile dest + showNote "copying..." + ok <- liftIO $ boolSystem "cp" ["-p", src, dest] + if ok + then do + liftIO $ allowWrite dest + return $ Just $ return True + else error "cp failed!" @@ -173,6 +173,7 @@ moveAnnex key src = do let dir = parentDir dest liftIO $ do createDirectoryIfMissing True dir + allowWrite dir -- in case the directory already exists renameFile src dest preventWrite dest preventWrite dir diff --git a/GitRepo.hs b/GitRepo.hs index da86c225e..5fc077c44 100644 --- a/GitRepo.hs +++ b/GitRepo.hs @@ -39,6 +39,7 @@ module GitRepo ( checkAttr, decodeGitFile, encodeGitFile, + typeChangedFiles, prop_idempotent_deencode ) where @@ -58,6 +59,7 @@ import Data.Char import Data.Word (Word8) import Codec.Binary.UTF8.String (encode) import Text.Printf +import Data.List import Utility @@ -227,20 +229,31 @@ hPipeRead repo params = assertLocal repo $ do - are checked into git at that location. -} inRepo :: Repo -> FilePath -> IO [FilePath] inRepo repo l = pipeNullSplit repo - ["ls-files", "--cached", "--exclude-standard", "-z", l] + ["ls-files", "--cached", "--exclude-standard", "-z", "--", l] {- Passed a location, recursively scans for all files that are not checked - into git, and not gitignored. -} notInRepo :: Repo -> FilePath -> IO [FilePath] notInRepo repo l = pipeNullSplit repo - ["ls-files", "--others", "--exclude-standard", "-z", l] + ["ls-files", "--others", "--exclude-standard", "-z", "--", l] {- Passed a location, returns a list of the files, staged for - commit, that are being added, moved, or changed (but not deleted). -} stagedFiles :: Repo -> FilePath -> IO [FilePath] stagedFiles repo l = pipeNullSplit repo - ["diff", "--cached", "--name-only", "--diff-filter=ACMRT", "-z", - "HEAD", l] + ["diff", "--cached", "--name-only", "--diff-filter=ACMRT", "-z", + "--", l] + +{- Passed a location, returns a list of the files whose type has changed. -} +typeChangedFiles :: Repo -> FilePath -> IO [FilePath] +typeChangedFiles repo l = do + changed <- pipeNullSplit repo $ start ++ end + changedCached <- pipeNullSplit repo $ start ++ ["--cached"] ++ end + -- a file can be found twice by the above, so nub + return $ nub $ changed ++ changedCached + where + start = ["diff", "--name-only", "--diff-filter=T", "-z"] + end = ["--", l] {- Reads null terminated output of a git command (as enabled by the -z - parameter), and splits it into a list of files. -} @@ -1,8 +1,10 @@ all: git-annex docs +ghcmake=ghc -Wall -odir build -hidir build --make + git-annex: mkdir -p build - ghc -Wall -odir build -hidir build --make git-annex + $(ghcmake) git-annex install: install -d $(DESTDIR)/usr/bin @@ -17,7 +19,8 @@ IKIWIKI=ikiwiki endif test: - runghc test.hs + $(ghcmake) test + ./test docs: ./mdwn2man git-annex 1 doc/git-annex.mdwn > git-annex.1 @@ -27,7 +30,7 @@ docs: --disable-plugin=smiley clean: - rm -rf build git-annex git-annex.1 + rm -rf build git-annex git-annex.1 test rm -rf doc/.ikiwiki html -.PHONY: git-annex +.PHONY: git-annex test diff --git a/debian/changelog b/debian/changelog index 1ce6a2deb..d3733aeb8 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,5 +1,12 @@ git-annex (0.04) UNRELEASED; urgency=low + * Add unlock subcommand, which replaces the symlink with a copy of + the file's content in preparation of changing it. The "edit" subcommand + is an alias for unlock. + * Add lock subcommand. + * Unlocked files will now automatically be added back into the annex when + committed (and the updated symlink committed), by some magic in the + pre-commit hook. * Add build dep on libghc6-testpack-dev. * Add annex.version, which will be used to automate upgrades between incompatable versions. @@ -11,6 +18,9 @@ git-annex (0.04) UNRELEASED; urgency=low * Annexed file contents are now made unwritable and put in unwriteable directories, to avoid them accidentially being removed or modified. (Thanks Josh Triplett for the idea.) + * Avoid using runghc to run test suite as it is not available on all + architectures. Closes: #603006 + * Missing build dep. Closes: #603016 -- Joey Hess <joeyh@debian.org> Mon, 08 Nov 2010 12:36:39 -0400 diff --git a/doc/backends.mdwn b/doc/backends.mdwn index fde23df5e..be4eac723 100644 --- a/doc/backends.mdwn +++ b/doc/backends.mdwn @@ -17,7 +17,7 @@ can use different backends for different files. * `SHA1` -- This backend stores the file's content in `.git/annex/objects/`, with a name based on its sha1 checksum. This backend allows modifications of files to be tracked. Its need to generate checksums - can make it slower for large files. **Warning** this backend is not ready + can make it slower for large files. for use. * `URL` -- This backend downloads the file's content from an external URL. diff --git a/doc/git-annex.mdwn b/doc/git-annex.mdwn index 6a580f005..d0bd3a754 100644 --- a/doc/git-annex.mdwn +++ b/doc/git-annex.mdwn @@ -81,6 +81,19 @@ Many git-annex subcommands will stage changes for later `git commit` by you. git-annex may refuse to drop content if the backend does not think it is safe to do so, typically because of the setting of annex.numcopies. +* unlock [path ...] + + Normally, the content of annexed files is protected from being changed. + Unlocking a annexed file allows it to be modified. This replaces the + symlink for each specified file with a copy of the file's content. + You can then modify it and `git annex add` (or `git commit`) to inject + it back into the annex. + +* edit [path ...] + + This is an alias for the unlock subcommand. May be easier to remember, + if you think of this as allowing you to edit an annexed file. + * move [path ...] When used with the --to option, moves the content of annexed files from @@ -93,7 +106,11 @@ Many git-annex subcommands will stage changes for later `git commit` by you. Initializes git-annex with a description of the git repository, and sets up `.gitattributes` and the pre-commit hook. - This is an optional, but recommended step. + +* lock [path ...] + + Use this to undo an unlock command if you don't want to modify + the files, or have made modifications you want to discard. * unannex [path ...] @@ -110,7 +127,8 @@ Many git-annex subcommands will stage changes for later `git commit` by you. * pre-commit [path ...] Fixes up symlinks that are staged as part of a commit, to ensure they - point to annexed content. + point to annexed content. Also handles injecting changes to unlocked + files into the annex. This is meant to be called from git's pre-commit hook. `git annex init` automatically creates a pre-commit hook using this. diff --git a/doc/todo/backendSHA1.mdwn b/doc/todo/backendSHA1.mdwn index fa9728af6..8c16b75ad 100644 --- a/doc/todo/backendSHA1.mdwn +++ b/doc/todo/backendSHA1.mdwn @@ -3,3 +3,5 @@ This backend is not finished. In particular, while files can be added using it, git-annex will not notice when their content changes, and will not create a new key for the new sha1 of the net content. + +[[done]]; use unlock subcommand and commit changes with git diff --git a/doc/todo/checkout.mdwn b/doc/todo/checkout.mdwn index 70d31a5ff..50da2d62e 100644 --- a/doc/todo/checkout.mdwn +++ b/doc/todo/checkout.mdwn @@ -3,3 +3,21 @@ file's content, with a copy of the file. Once you've checked a file out, you can edit it, and `git commit` it. On commit, git-annex will detect if the file has been changed, and if it has, `add` its content to the annex. + +> Internally, this will need to store the original symlink to the file, in +> `.git/annex/checkedout/$filename`. +> +> * git-annex uncheckout moves that back +> * git-annex pre-commit hook checks each file being committed to see if +> it has a symlink there, and if so, removes the symlink and adds the new +> content to the annex. +> +> And it seems the file content should be copied, not moved or hard linked: +> +> * Makes sure other annexes can find it if transferring it from +> this annex. +> * Ensures it's always available for uncheckout. +> * Avoids the last copy of a file's content being lost when +> the checked out file is modified. + +[[done]] diff --git a/doc/walkthrough.mdwn b/doc/walkthrough.mdwn index ab0067470..61cf29b89 100644 --- a/doc/walkthrough.mdwn +++ b/doc/walkthrough.mdwn @@ -139,6 +139,50 @@ But `other.iso` looks to have never been copied to anywhere else, so if it's something you want to hold onto, you'd need to transfer it to some other repository before dropping it. +## modifying annexed files + +Normally, the content of files in the annex is prevented from being modified. + + # echo oops > my_cool_big_file + bash: my_cool_big_file: Permission deined + +In order to modify a file, it should first be unlocked. + + # git annex unlock my_cool_big_file + unlock my_cool_big_file (copying...) ok + +They replaces the symlink that normally points at its content with a copy +of the content. You can then modify the file like any regular file. Because +it is a regular file. + +If you decide you don't need to modify the file after all, or want to discard +modifications, just use `git annex lock`. + +When you `git commit`, git-annex's pre-commit hook will automatically +notice that you are committing an unlocked file, and add its new content +to the annex. The file will be replaced with a symlink to the new content, +and this symlink is what gets committed to git in the end. + + # echo "now smaller, but even cooler" > my_cool_big_file + # git commit my_cool_big_file -m "changed an annexed file" + add my_cool_big_file ok + [master 64cda67] changed an annexed file + 2 files changed, 2 insertions(+), 1 deletions(-) + create mode 100644 .git-annex/SHA1:0b1d8616d0238cb9418a0e0a649bdad2e9e7faae.log + +There is one problem with using `git commit` like this: Git wants to first +stage the entire contents of the file in its index. That can be slow for +big files (sorta why git-annex exists in the first place). So, the +automatic handling on commit is a nice safety feature, since it prevents +the file content being accidentially commited into git. But when working with +big files, it's faster to explicitly add them to the annex yourself +before committing. + + # echo "now smaller, but even cooler yet" > my_cool_big_file + # git annex add my_cool_big_file + add my_cool_big_file ok + # git commit my_cool_big_file -m "changed an annexed file" + ## using ssh remotes So far in this walkthrough, git-annex has been used with a remote @@ -216,3 +260,17 @@ that the URL is stable; no local backup is kept. # git annex drop somefile drop somefile (ok) + +## using the SHA1 backend + +Another handy alternative to the default [[backend|backends]] is the +SHA1 backend. This backend provides more git-style assurance that your data +has not been damanged. And the checksum means that when you add the same +content to the annex twice, only one copy need be stored in the backend. + +The only reason it's not the default is that it needs to checksum +files when they're added to the annex, and this can slow things down +significantly for really big files. To make SHA1 the detault, just +add something like this to `.gitattributes`: + + * git-annex-backend=SHA1 @@ -1,15 +1,16 @@ -- TODO find a test harness that is actually in Debian and use it. -import Test.QuickCheck import Test.HUnit import Test.HUnit.Tools import GitRepo import Locations +alltests :: [Test] alltests = [ qctest "prop_idempotent_deencode" prop_idempotent_deencode, qctest "prop_idempotent_fileKey" prop_idempotent_fileKey ] +main :: IO (Counts, Int) main = runVerboseTests (TestList alltests) |