101-136-filesystem.rst 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167
  1. .. _file system:
  2. Miscellaneous file system operations
  3. ------------------------------------
  4. With all of the information about symlinks and object trees,
  5. you might be reluctant to perform usual file system managing
  6. operations, such as copying, moving, renaming or deleting
  7. files or directories with annexed content.
  8. If I renamed one of those books, would the symlink that points
  9. to the file content still be correct? What happens if I'd copy
  10. an annexed file?
  11. If I moved the whole ``books/`` directory? What if I moved
  12. all of ``DataLad-101`` into a different place on my computer?
  13. What if renamed the whole superdataset?
  14. And how do I remove a file, or directory, or subdataset?
  15. Therefore, there is an extra tutorial today, and you attend.
  16. There is no better way of learning than doing. Here, in the
  17. safe space of the ``DataLad-101`` course, you can try out all
  18. of the things you would be unsure about or reluctant to try
  19. on the dataset that contains your own, valuable data.
  20. Below you will find common questions about file system
  21. management operations, and each question outlines caveats and
  22. solutions with code examples you can paste into your own terminal.
  23. Because these code snippets will add many commits to your
  24. dataset, we are cleaning up within each segment with
  25. common Git operations that manipulate the datasets
  26. history -- be sure to execute these commands as well (and
  27. be sure to be in the correct dataset).
  28. .. index::
  29. pair: rename file; with DataLad
  30. Renaming files
  31. ^^^^^^^^^^^^^^
  32. Let's try it. In Unix, renaming a file is exactly the same as
  33. moving a file, and uses the :shcmd:`mv` command.
  34. .. runrecord:: _examples/DL-101-136-101
  35. :language: console
  36. :workdir: dl-101/DataLad-101
  37. :realcommand: cd books/ && mv TLCL.pdf The_Linux_Command_Line.pdf && ls -lh --time-style=long-iso
  38. :notes: Let's look into file system operations. What does renaming does to a file that is symlinked?
  39. :cast: 03_git_annex_basics
  40. $ cd books/
  41. $ mv TLCL.pdf The_Linux_Command_Line.pdf
  42. $ ls -lh The_Linux_Command_Line.pdf
  43. Try to open the renamed file, e.g., with
  44. ``evince The_Linux_Command_Line.pdf``.
  45. This works!
  46. But let's see what changed in the dataset with this operation:
  47. .. runrecord:: _examples/DL-101-136-102
  48. :language: console
  49. :workdir: dl-101/DataLad-101/books
  50. :notes: how does datalad see this? (deleted and untracked -- weird!!)
  51. :cast: 03_git_annex_basics
  52. $ datalad status
  53. We can see that the old file is marked as ``deleted``, and
  54. simultaneously, an ``untracked`` file appears: the renamed
  55. PDF.
  56. While this might appear messy, a ``datalad save`` will clean
  57. all of this up. Therefore, do not panic if you rename a file,
  58. and see a dirty dataset status with deleted and untracked files
  59. -- ``datalad save`` handles these and other cases really well
  60. under the hood.
  61. .. runrecord:: _examples/DL-101-136-103
  62. :language: console
  63. :workdir: dl-101/DataLad-101/books
  64. :notes: datalad save rectifies the weird status
  65. :cast: 03_git_annex_basics
  66. $ datalad save -m "rename the book"
  67. The :dlcmd:`save` command will identify that a file was
  68. renamed, and will summarize this nicely in the resulting commit:
  69. .. runrecord:: _examples/DL-101-136-104
  70. :language: console
  71. :workdir: dl-101/DataLad-101/books
  72. :emphasize-lines: 8-11
  73. :notes: and this is how it looks like in the history
  74. :cast: 03_git_annex_basics
  75. $ git log -n 1 -p
  76. Note that :dlcmd:`save` commits all modifications when
  77. it's called without a path specification,
  78. so any other changes will be saved in the same commit as the rename.
  79. If there are unsaved modifications you do not want to commit
  80. together with the file name change, you could give both the
  81. new and the deleted file as a path specification to
  82. :dlcmd:`save`, even if it feels unintuitive to
  83. save a change that is marked as a deletion in a
  84. :dlcmd:`status`:
  85. .. code-block:: console
  86. $ datalad save -m "rename file" oldname newname
  87. Alternatively, there is also a way to save the name change
  88. only using Git tools only, outlined in the :find-out-more:`on faster renaming <fom-gitmv>`. If you are a Git user, you will be very familiar with it.
  89. .. index::
  90. pair: rename file; with Git
  91. .. find-out-more:: Faster renaming with Git tools
  92. :name: fom-gitmv
  93. Git has built-in commands that provide a solution in two steps.
  94. If you have followed along with the previous :dlcmd:`save`, let's revert the renaming of the the files:
  95. .. runrecord:: _examples/DL-101-136-105
  96. :language: console
  97. :workdir: dl-101/DataLad-101/books
  98. :notes: We can also rename with git tools. first: reset history
  99. :cast: 03_git_annex_basics
  100. $ git reset --hard HEAD~1
  101. $ datalad status
  102. Now we are checking out how to rename files and commit this operation
  103. using only Git:
  104. A Git-specific way to rename files is the ``git mv`` command:
  105. .. runrecord:: _examples/DL-101-136-106
  106. :language: console
  107. :workdir: dl-101/DataLad-101/books
  108. :notes: we use "git mv" instead of "mv" to rename
  109. :cast: 03_git_annex_basics
  110. $ git mv TLCL.pdf The_Linux_Command_Line.pdf
  111. .. runrecord:: _examples/DL-101-136-107
  112. :language: console
  113. :workdir: dl-101/DataLad-101/books
  114. :notes: how does the modification appear to datalad now?
  115. :cast: 03_git_annex_basics
  116. $ datalad status
  117. We can see that the old file is still seen as "deleted", but the "new",
  118. renamed file is "added". A ``git status`` displays the change
  119. in the dataset a bit more accurately:
  120. .. runrecord:: _examples/DL-101-136-108
  121. :language: console
  122. :workdir: dl-101/DataLad-101/books
  123. :notes: how does the modification appear to git?
  124. :cast: 03_git_annex_basics
  125. $ git status
  126. Because the :gitcmd:`mv` places the change directly into the
  127. staging area (the *index*) of Git [#f1]_,
  128. a subsequent ``git commit -m "rename book"`` will write the renaming
  129. -- and only the renaming -- to the dataset's history, even if other
  130. (unstaged) modifications are present.
  131. .. runrecord:: _examples/DL-101-136-109
  132. :language: console
  133. :workdir: dl-101/DataLad-101/books
  134. :notes: git mv put the modification to the staging area, we need to commit
  135. :cast: 03_git_annex_basics
  136. $ git commit -m "rename book"
  137. `Especially when renaming directories with many files, this can be much faster <https://knowledge-base.psychoinformatics.de/kbi/0022>`_ than a ``mv`` followed by ``datalad save``,
  138. To summarize, renaming files is easy and worry-free. Do not be intimidated
  139. by a file marked as deleted -- a :dlcmd:`save` will rectify this.
  140. Be mindful of other modifications in your dataset, though, and either supply
  141. appropriate paths to ``datalad save``, or use Git tools to exclusively save
  142. the name change and nothing else.
  143. Let's revert this now, to have a clean history.
  144. .. runrecord:: _examples/DL-101-136-110
  145. :language: console
  146. :workdir: dl-101/DataLad-101/books
  147. :notes: (reverting again for clean history)
  148. :cast: 03_git_annex_basics
  149. $ git reset --hard HEAD~1
  150. $ datalad status
  151. Moving files from or into subdirectories
  152. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  153. Let's move an annexed file from within ``books/`` into the root
  154. of the superdataset:
  155. .. runrecord:: _examples/DL-101-136-120
  156. :language: console
  157. :workdir: dl-101/DataLad-101/books
  158. :notes: Renaming was easy. How does moving files into different directories look like?
  159. :cast: 03_git_annex_basics
  160. $ mv TLCL.pdf ../TLCL.pdf
  161. $ datalad status
  162. In general, this looks exactly like renaming or moving a file
  163. in the same directory. There is a subtle difference though:
  164. Currently, the symlink of the annexed file is broken. There
  165. are two ways to demonstrate this. One is trying to open the
  166. file -- this will currently fail. The second way is to look
  167. at the symlink:
  168. .. runrecord:: _examples/DL-101-136-121
  169. :language: console
  170. :workdir: dl-101/DataLad-101/books
  171. :realcommand: cd .. && ls -l --time-style=long-iso TLCL.pdf
  172. :notes: currently the symlink is broken! it points into nowhere
  173. :cast: 03_git_annex_basics
  174. $ cd ../
  175. $ ls -l TLCL.pdf
  176. The first part of the symlink should point into the ``.git/``
  177. directory, but currently, it does not -- the symlink still looks
  178. like ``TLCL.pdf`` would be within ``books/``. Instead of pointing
  179. into ``.git``, it currently points to ``../.git``, which is non-existent,
  180. and even outside of the superdataset. This is why the file
  181. cannot be opened: When any program tries to follow the symlink,
  182. it will not resolve, and an error such as "no file or directory"
  183. will be returned. But do not panic! A :dlcmd:`save` will
  184. rectify this as well:
  185. .. runrecord:: _examples/DL-101-136-122
  186. :language: console
  187. :workdir: dl-101/DataLad-101
  188. :realcommand: datalad save -m "moved book into root" && ls -l --time-style=long-iso TLCL.pdf
  189. :notes: but a save rectifies it
  190. :cast: 03_git_annex_basics
  191. $ datalad save -m "moved book into root"
  192. $ ls -l TLCL.pdf
  193. After a ``datalad save``, the symlink is fixed again.
  194. Therefore, in general, whenever moving or renaming a file,
  195. especially between directories, a ``datalad save`` is
  196. the best option to turn to.
  197. .. index::
  198. pair: content pointer file; git-annex concept
  199. .. find-out-more:: Why a move between directories is actually a content change
  200. Let's see how this shows up in the dataset history:
  201. .. runrecord:: _examples/DL-101-136-123
  202. :language: console
  203. :workdir: dl-101/DataLad-101/books
  204. :notes: moving files across directory levels is a content change because the symlink changes!
  205. :cast: 03_git_annex_basics
  206. $ git log -n 1 -p
  207. As you can see, this action does not show up as a move, but instead
  208. a deletion and addition of a new file. Why? Because the content
  209. that is tracked is the actual symlink, and due to the change in
  210. relative location, the symlink needed to change. Hence, what looks
  211. and feels like a move on the file system for you is actually a
  212. move plus a content change for Git.
  213. .. index::
  214. pair: fix; git-annex command
  215. .. gitusernote:: 'datalad save' internals: 'git annex fix'
  216. A :dlcmd:`save` command internally uses a :gitcmd:`commit` to save changes to a dataset.
  217. :gitcmd:`commit` in turn triggers a :gitannexcmd:`fix`
  218. command. This git-annex command fixes up links that have become broken
  219. to again point to annexed content, and is responsible for cleaning up
  220. what needs to be cleaned up. Thanks, git-annex!
  221. Finally, let's clean up:
  222. .. runrecord:: _examples/DL-101-136-124
  223. :language: console
  224. :workdir: dl-101/DataLad-101
  225. :notes: (reset history)
  226. :cast: 03_git_annex_basics
  227. $ git reset --hard HEAD~1
  228. .. index::
  229. pair: move file to other dataset; with DataLad
  230. Moving files across dataset boundaries
  231. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  232. Generally speaking, moving files across dataset hierarchies is not advised.
  233. While DataLad blurs the dataset boundaries to ease working in nested dataset,
  234. the dataset boundaries do still exist. If you move a file from one subdataset
  235. into another, or up or down a dataset hierarchy, you will move it out of the
  236. version control it was in (i.e., from one ``.git`` directory into a different
  237. one). From the perspective of the first subdataset, the file will be deleted,
  238. and from the perspective of the receiving dataset, the file will be added to
  239. the dataset, but straight out of nowhere, with none of its potential history
  240. from its original dataset attached to it. Before moving a file, consider whether
  241. *copying* it (outlined in the next but one paragraph) might be a more suitable
  242. alternative.
  243. If you are willing to sacrifice [#f2]_ the file's history and move it to a
  244. different dataset, the procedure differs between annexed files, and files
  245. stored in Git.
  246. For files that Git manages, moving and saving is simple: Move the file, and
  247. save the resulting changes in *both* affected datasets (this can be done with
  248. a recursive :dlcmd:`save` from a top-level dataset, though).
  249. .. runrecord:: _examples/DL-101-136-125
  250. :language: console
  251. :workdir: dl-101/DataLad-101
  252. :notes: move files across dataset boundaries
  253. :cast: 03_git_annex_basics
  254. $ mv notes.txt midterm_project/notes.txt
  255. $ datalad status -r
  256. .. runrecord:: _examples/DL-101-136-127
  257. :language: console
  258. :workdir: dl-101/DataLad-101
  259. :notes: save recursively
  260. :cast: 03_git_annex_basics
  261. $ datalad save -r -m "moved notes.txt from root of top-ds to midterm subds"
  262. Note how the history of ``notes.txt`` does not exist in the subdataset -- it appears
  263. as if the file was generated at once, instead of successively over the course:
  264. .. runrecord:: _examples/DL-101-136-128
  265. :language: console
  266. :workdir: dl-101/DataLad-101
  267. :notes: show history is vanished
  268. :cast: 03_git_annex_basics
  269. $ cd midterm_project
  270. $ git log notes.txt
  271. (Undo-ing this requires ``git reset``\s in *both* datasets)
  272. .. runrecord:: _examples/DL-101-136-129
  273. :language: console
  274. :workdir: dl-101/DataLad-101/midterm_project
  275. :notes: clean-up
  276. :cast: 03_git_annex_basics
  277. $ # in midterm_project
  278. $ git reset --hard HEAD~
  279. $ # in DataLad-101
  280. $ cd ../
  281. $ git reset --hard HEAD~
  282. The process is a bit more complex for annexed files. Let's do it wrong, first:
  283. What happens if we move an annexed file in the same way as ``notes.txt``?
  284. .. runrecord:: _examples/DL-101-136-130
  285. :language: console
  286. :workdir: dl-101/DataLad-101
  287. :notes: move an annexed file wrongly
  288. :cast: 03_git_annex_basics
  289. $ mv books/TLCL.pdf midterm_project
  290. $ datalad status -r
  291. .. runrecord:: _examples/DL-101-136-131
  292. :language: console
  293. :workdir: dl-101/DataLad-101
  294. :notes: save - wrong way still
  295. :cast: 03_git_annex_basics
  296. $ datalad save -r -m "move annexed file around"
  297. At this point, this does not look that different to the result of moving
  298. ``notes.txt``. Note, though, that the deleted and untracked PDFs are symlinks --
  299. and therein lies the problem: What was moved was not the file content (which is
  300. still in the annex of the top-level dataset, ``DataLad-101``), but its symlink that
  301. was stored in Git. After moving the file, the symlink is broken, and git-annex
  302. has no way of finding out where the file content could be:
  303. .. runrecord:: _examples/DL-101-136-132
  304. :language: console
  305. :workdir: dl-101/DataLad-101
  306. :exitcode: 1
  307. :notes: demonstrate broken symlink with git-annex-whereis
  308. :cast: 03_git_annex_basics
  309. $ cd midterm_project
  310. $ git annex whereis TLCL.pdf
  311. Let's rewind, and find out how to do it correctly:
  312. .. runrecord:: _examples/DL-101-136-133
  313. :language: console
  314. :workdir: dl-101/DataLad-101/midterm_project
  315. :notes: undo wrong moving of annex file
  316. :cast: 03_git_annex_basics
  317. $ git reset --hard HEAD~
  318. $ cd ../
  319. $ git reset --hard HEAD~
  320. The crucial step to remember is to get the annexed file out of the annex prior
  321. to moving it. For this, we need to fall back to git-annex commands:
  322. .. runrecord:: _examples/DL-101-136-134
  323. :language: console
  324. :workdir: dl-101/DataLad-101
  325. :notes: unannex file
  326. :cast: 03_git_annex_basics
  327. $ git annex unlock books/TLCL.pdf
  328. $ mv books/TLCL.pdf midterm_project
  329. $ datalad status -r
  330. Afterwards, a (recursive) :dlcmd:`save` commits the removal of the book from
  331. DataLad-101, and adds the file content into the annex of ``midterm_project``:
  332. .. runrecord:: _examples/DL-101-136-135
  333. :language: console
  334. :workdir: dl-101/DataLad-101
  335. :notes: save annex file after moving it to subdataset
  336. $ datalad save -r -m "move book into midterm_project"
  337. Even though you did split the file's history, at least its content is in the
  338. correct dataset now:
  339. .. runrecord:: _examples/DL-101-136-136
  340. :language: console
  341. :workdir: dl-101/DataLad-101
  342. :notes: show that moving after unannex worked with git annex whereis
  343. $ cd midterm_project
  344. $ git annex whereis TLCL.pdf
  345. But more than showing you how it can be done, if necessary, this paragraph
  346. hopefully convinced you that moving files across dataset boundaries is not
  347. convenient. It can be a confusing and potentially "file-content-losing"-dangerous
  348. process, but it also dissociates a file from its provenance that is captured
  349. in its previous dataset, with no machine-readable way to learn about the move
  350. easily. A better alternative may be copying files with the :dlcmd:`copy-file`
  351. command introduced in detail in the online-handbook, and demonstrated in the next
  352. but one paragraph. Let's quickly clean up by moving the file back:
  353. .. runrecord:: _examples/DL-101-136-137
  354. :language: console
  355. :workdir: dl-101/DataLad-101/midterm_project
  356. :notes: move file back
  357. $ # in midterm_project
  358. $ git annex unannex TLCL.pdf
  359. .. runrecord:: _examples/DL-101-136-138
  360. :language: console
  361. :workdir: dl-101/DataLad-101/midterm_project
  362. :notes: move file back
  363. $ mv TLCL.pdf ../books
  364. $ cd ../
  365. $ datalad save -r -m "move book back from midterm_project"
  366. Copying files
  367. ^^^^^^^^^^^^^
  368. Let's create a copy of an annexed file, using the Unix
  369. command ``cp`` to copy.
  370. .. runrecord:: _examples/DL-101-136-140
  371. :language: console
  372. :workdir: dl-101/DataLad-101
  373. :notes: renaming and moving was fine, how about copying?
  374. :cast: 03_git_annex_basics
  375. $ cp books/TLCL.pdf copyofTLCL.pdf
  376. $ datalad status
  377. That's expected. The copy shows up as a new, untracked
  378. file. Let's save it:
  379. .. runrecord:: _examples/DL-101-136-141
  380. :language: console
  381. :workdir: dl-101/DataLad-101
  382. :notes: status says there's an untracked file, let's save it
  383. :cast: 03_git_annex_basics
  384. $ datalad save -m "add copy of TLCL.pdf"
  385. .. runrecord:: _examples/DL-101-136-142
  386. :language: console
  387. :workdir: dl-101/DataLad-101
  388. :notes: That's it!
  389. :cast: 03_git_annex_basics
  390. $ git log -n 1 -p
  391. That's it.
  392. .. index::
  393. pair: content pointer file; git-annex concept
  394. .. find-out-more:: Symlinks!
  395. If you have read the additional content in the section
  396. :ref:`symlink`, you know that the same file content
  397. is only stored once, and copies of the same file point to
  398. the same location in the object tree.
  399. Let's check that out:
  400. .. runrecord:: _examples/DL-101-136-143
  401. :language: console
  402. :workdir: dl-101/DataLad-101
  403. :realcommand: ls -l --time-style=long-iso copyofTLCL.pdf && ls -l --time-style=long-iso books/TLCL.pdf
  404. :notes: A cool thing is that the two identical files link to the same place in the object tree
  405. :cast: 03_git_annex_basics
  406. $ ls -l copyofTLCL.pdf
  407. $ ls -l books/TLCL.pdf
  408. Indeed! Apart from their relative location (``.git`` versus
  409. ``../.git``) their symlink is identical. Thus, even though two
  410. copies of the book exist in your dataset, your disk needs to
  411. store it only once.
  412. In most cases, this is just an interesting fun-fact, but beware
  413. when dropping content with :dlcmd:`drop`
  414. as outlined in :ref:`remove`:
  415. If you drop the content of one copy of a file, all
  416. other copies will lose this content as well.
  417. Finally, let's clean up:
  418. .. runrecord:: _examples/DL-101-136-144
  419. :language: console
  420. :workdir: dl-101/DataLad-101
  421. :notes: (reset history)
  422. :cast: 03_git_annex_basics
  423. $ git reset --hard HEAD~1
  424. .. _copyfileFS:
  425. .. index::
  426. pair: copy file to other dataset; with DataLad
  427. Copying files across dataset boundaries
  428. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  429. Instead of moving files across dataset boundaries, *copying* them is an easier
  430. and actually supported method.
  431. The DataLad command that can be used for this is :dlcmd:`copy-file`.
  432. This command allows to copy files
  433. (from any dataset or non-dataset location, annexed or not annexed) into a dataset.
  434. If the file is copied from a dataset and is annexed, its availability metadata
  435. is added to the new dataset as well, and there is no need for unannex'ing the
  436. or even retrieving its file contents. Let's see this in action for a file
  437. stored in Git, and a file stored in annex:
  438. .. runrecord:: _examples/DL-101-136-145
  439. :language: console
  440. :workdir: dl-101/DataLad-101
  441. $ datalad copy-file notes.txt midterm_project -d midterm_project
  442. .. runrecord:: _examples/DL-101-136-146
  443. :language: console
  444. :workdir: dl-101/DataLad-101
  445. $ datalad copy-file books/bash_guide.pdf midterm_project -d midterm_project
  446. Both files have been successfully transferred and saved to the subdataset, and
  447. no unannexing was necessary.
  448. ``notes.txt`` was annexed in the subdataset, though, as this subdataset
  449. was not configured with the ``text2git`` :term:`run procedure`.
  450. .. runrecord:: _examples/DL-101-136-147
  451. :language: console
  452. :workdir: dl-101/DataLad-101
  453. :emphasize-lines: 3, 10
  454. $ tree midterm_project
  455. The subdataset has two new commits as :dlcmd:`copy-file` can take care
  456. of saving changes in the copied-to dataset, and thus the new subdataset state
  457. would need to be saved in the superdataset.
  458. .. runrecord:: _examples/DL-101-136-148
  459. :language: console
  460. :workdir: dl-101/DataLad-101
  461. $ datalad status -r
  462. Still, just as when we *moved* files across dataset boundaries, the files'
  463. provenance record is lost:
  464. .. runrecord:: _examples/DL-101-136-149
  465. :language: console
  466. :workdir: dl-101/DataLad-101
  467. $ cd midterm_project
  468. $ git log notes.txt
  469. Nevertheless, copying files with :dlcmd:`copy-file` is easier and safer
  470. than moving them with standard Unix commands, especially so for annexed files.
  471. A more detailed introduction to :dlcmd:`copy-file` and a concrete
  472. use case can be found in the online-handbook.
  473. Let's clean up:
  474. .. runrecord:: _examples/DL-101-136-150
  475. :language: console
  476. :workdir: dl-101/DataLad-101/midterm_project
  477. $ git reset --hard HEAD~2
  478. Moving/renaming a subdirectory or subdataset
  479. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  480. Moving or renaming subdirectories, especially if they are subdatasets,
  481. *can* be a minefield. But in principle, a safe way to proceed is using
  482. the Unix :shcmd:`mv` command to move or rename, and the :dlcmd:`save`
  483. to clean up afterwards, just as in the examples above. Make sure to
  484. **not** use ``git mv``, especially for subdatasets.
  485. Let's, for example, rename the ``books`` directory:
  486. .. runrecord:: _examples/DL-101-136-151
  487. :language: console
  488. :workdir: dl-101/DataLad-101
  489. :notes: renaming and moving subdirectories and subdatasets can be a minefield, but is usually okay: let's change the name of books to readings
  490. :cast: 03_git_annex_basics
  491. $ mv books/ readings
  492. $ datalad status
  493. .. runrecord:: _examples/DL-101-136-152
  494. :language: console
  495. :workdir: dl-101/DataLad-101
  496. :notes: a save rectifies everything
  497. :cast: 03_git_annex_basics
  498. $ datalad save -m "renamed directory"
  499. This is easy, and complication free. Moving (as in: changing the location, instead of
  500. the name) the directory would work in the
  501. same fashion, and a :dlcmd:`save` would fix broken symlinks afterwards.
  502. Let's quickly clean this up:
  503. .. runrecord:: _examples/DL-101-136-153
  504. :language: console
  505. :workdir: dl-101/DataLad-101
  506. :notes: (quickly clean up)
  507. :cast: 03_git_annex_basics
  508. $ git reset --hard HEAD~1
  509. But let's now try to move the ``longnow`` subdataset into the root of the
  510. superdataset:
  511. .. runrecord:: _examples/DL-101-136-154
  512. :language: console
  513. :workdir: dl-101/DataLad-101
  514. :notes: But what about renaming or moving a subdataset? Let's move longnow into the root of the dataset
  515. :cast: 03_git_annex_basics
  516. $ mv recordings/longnow .
  517. $ datalad status
  518. .. runrecord:: _examples/DL-101-136-155
  519. :language: console
  520. :workdir: dl-101/DataLad-101
  521. :notes: a save will work and rectify things ...
  522. :cast: 03_git_annex_basics
  523. $ datalad save -m "moved subdataset"
  524. .. runrecord:: _examples/DL-101-136-156
  525. :language: console
  526. :workdir: dl-101/DataLad-101
  527. :cast: 03_git_annex_basics
  528. $ datalad status
  529. This seems fine, and it has indeed worked.
  530. However, *reverting* a commit like this is tricky, at the moment. This could
  531. lead to trouble if you at a later point try to revert or rebase chunks of your
  532. history including this move. Therefore, if you can, try not to move subdatasets
  533. around. For now we'll clean up in a somewhat "hacky" way: Reverting, and
  534. moving remaining subdataset contents back to their original place by hand
  535. to take care of the unwanted changes the commit reversal introduced.
  536. .. runrecord:: _examples/DL-101-136-157
  537. :language: console
  538. :workdir: dl-101/DataLad-101
  539. :notes: BUT reverting such a commit in the history can be tricky atm:
  540. :cast: 03_git_annex_basics
  541. $ git reset --hard HEAD~1
  542. .. runrecord:: _examples/DL-101-136-158
  543. :language: console
  544. :workdir: dl-101/DataLad-101
  545. :notes: we have to move the remaining subdataset contents back to the original place
  546. :cast: 03_git_annex_basics
  547. $ mv -f longnow recordings
  548. The take-home message therefore is that it is best not to move subdatasets,
  549. but very possible to move subdirectories if necessary. In both cases, do not
  550. attempt moving with the :gitcmd:`mv`, but stick with :shcmd:`mv` and
  551. a subsequent :dlcmd:`save`.
  552. .. todo::
  553. Update this when progress has been made towards
  554. https://github.com/datalad/datalad/issues/3464
  555. Moving/renaming a superdataset
  556. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  557. Once created, a DataLad superdataset may not be in an optimal
  558. place on your file system, or have the best name.
  559. After a while, you might think that the dataset would fit much
  560. better into ``/home/user/research_projects/`` than in
  561. ``/home/user/Documents/MyFiles/tmp/datalad-test/``. Or maybe at
  562. some point, a long name such as ``My-very-first-DataLad-project-wohoo-I-am-so-excited``
  563. does not look pretty in your terminal prompt anymore, and going for
  564. ``finance-2019`` seems more professional.
  565. These will be situations in which you want to rename or move
  566. a superdataset. Will that break anything?
  567. In all standard situations, no, it will be completely fine.
  568. You can use standard Unix commands such as ``mv`` to do it,
  569. and also whichever graphical user interface or explorer you may
  570. use.
  571. Beware of one thing though: If your dataset either is a sibling
  572. or has a sibling with the source being a path, moving or renaming
  573. the dataset will break the linkage between the datasets. This can
  574. be fixed easily though. We can try this in the :find-out-more:`on adjusting sibling URLs <fom-adjust-sibling-urls>`.
  575. .. index::
  576. pair: move subdataset; with Git
  577. .. find-out-more:: If a renamed/moved dataset is a sibling...
  578. :name: fom-adjust-sibling-urls
  579. .. include:: topic/moved-sibling-path-fix.rst
  580. Getting contents out of git-annex
  581. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  582. Files in your dataset can either be handled by :term:`Git` or :term:`Git-annex`.
  583. Self-made or predefined configurations to ``.gitattributes``, defaults, or the
  584. ``--to-git`` option to :dlcmd:`save` allow you to control which tool
  585. does what on up to single-file basis. Accidentally though, you may give a file of yours
  586. to git-annex when it was intended to be stored in Git, or you want to get a previously
  587. annexed file into Git.
  588. Consider you intend to share the cropped ``.png`` images you created from the
  589. ``longnow`` logos. Would you publish your ``DataLad-101`` dataset so :term:`GitHub`
  590. or :term:`GitLab`, these files would not be available to others, because annexed
  591. dataset contents cannot be published to these services.
  592. Even though you could find a third party service of your choice
  593. and publish your dataset *and* the annexed data as described in :ref:`sharethirdparty`,
  594. you are feeling lazy today. And since it
  595. is only two files, and they are quite small, you decide to store them in Git --
  596. this way, the files would be available without configuring an external data
  597. store.
  598. To get a file out of the git-annex hands you need to *unannex* it. This is
  599. done with the git-annex command :gitannexcmd:`unannex`. Let's see how it
  600. works:
  601. .. runrecord:: _examples/DL-101-136-167
  602. :language: console
  603. :workdir: dl-101/DataLad-101
  604. $ git annex unannex recordings/*logo_small.jpg
  605. Your dataset notices the unannexing of the files as follows.
  606. .. runrecord:: _examples/DL-101-136-168
  607. :language: console
  608. :workdir: dl-101/DataLad-101
  609. $ git status
  610. Once files have been unannexed, they are "untracked" again, and you can save them
  611. into Git, either by adding a rule to ``.gitattributes``, or with
  612. :dlcmd:`save --to-git`:
  613. .. runrecord:: _examples/DL-101-136-169
  614. :language: console
  615. :workdir: dl-101/DataLad-101
  616. $ datalad save --to-git -m "save cropped logos to Git" recordings/*jpg
  617. Note that git-annex keeps the previously annexed file's content in the annex for safety, to prevent accidental data loss.
  618. If it is only few and small files that were unannexed, their size in the annex will not matter much.
  619. If it is a lot of files or larger files that were accidentally annexed, you may want to drop the left-behind content using ``git annex unused`` and ``git annex dropunused``.
  620. .. _uninit:
  621. Getting all content out of the annex (removing the annex repo)
  622. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  623. In case you want to get all annexed contents out of a Dataset at once, you could turn to `git annex uninit <https://git-annex.branchable.com/git-annex-uninit>`_.
  624. It is a command that can be used to stop using git annex entirely in a given repository/dataset.
  625. Running this command will unannex every file in the repository, remove all of git-annex's other data, and remove the :term:`git-annex` branch, leaving you with a normal Git repository plus the previously annexed files.
  626. Note a ``datalad push`` will reinstate the git-annex branch *if* your dataset has siblings that still contain the annex branch.
  627. Deleting (annexed) files/directories
  628. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  629. Removing annexed file content from a dataset is possible in two different ways:
  630. Either by removing the file from the current state of the repository
  631. (which Git calls the *worktree*) but keeping the content in the history
  632. of the dataset, or by removing content entirely from a dataset and its
  633. history.
  634. Removing a file, but keeping content in history
  635. """""""""""""""""""""""""""""""""""""""""""""""
  636. An ``rm <file>`` or ``rm -rf <directory>`` with a subsequent :dlcmd:`save`
  637. will remove a file or directory, and save its removal. The file content however will
  638. still be in the history of the dataset, and the file can be brought back to existence
  639. by going back into the history of the dataset or reverting the removal commit:
  640. .. runrecord:: _examples/DL-101-136-170
  641. :workdir: dl-101/DataLad-101
  642. :notes: 2 ways to remove a file from dataset: remove the file from the current state of the repository (the *worktree*) but keeping the content in the history, or remove content entirely from a dataset and its history.
  643. :cast: 03_git_annex_basics
  644. $ # download a file
  645. $ datalad download-url -m "Added flower mosaic from wikimedia" \
  646. https://upload.wikimedia.org/wikipedia/commons/a/a5/Flower_poster_2.jpg \
  647. --path flowers.jpg
  648. $ ls -l flowers.jpg
  649. .. runrecord:: _examples/DL-101-136-171
  650. :workdir: dl-101/DataLad-101
  651. :language: console
  652. :cast: 03_git_annex_basics
  653. $ # removal is easy:
  654. $ rm flowers.jpg
  655. This will lead to a dirty dataset status:
  656. .. runrecord:: _examples/DL-101-136-172
  657. :workdir: dl-101/DataLad-101
  658. :language: console
  659. :notes: the deletion looks like this for datalad
  660. :cast: 03_git_annex_basics
  661. $ datalad status
  662. If a removal happened by accident, a ``git checkout -- flowers.jpg`` would undo
  663. the removal at this stage. To stick with the removal and clean up the dataset
  664. state, :dlcmd:`save` will suffice:
  665. .. runrecord:: _examples/DL-101-136-173
  666. :workdir: dl-101/DataLad-101
  667. :language: console
  668. :notes: a save will write the deletion of the file to history, but keep the content.
  669. :cast: 03_git_annex_basics
  670. $ datalad save -m "removed file again"
  671. This commits the deletion of the file in the dataset's history.
  672. If this commit is reverted, the file comes back to existence:
  673. .. runrecord:: _examples/DL-101-136-174
  674. :language: console
  675. :workdir: dl-101/DataLad-101
  676. :emphasize-lines: 6
  677. :notes: reverting the last action will bring back the file content:
  678. :cast: 03_git_annex_basics
  679. $ git reset --hard HEAD~1
  680. $ ls
  681. In other words, with an :shcmd:`rm` and subsequent :dlcmd:`save`,
  682. the symlink is removed, but the content is retained in the history.
  683. .. index::
  684. pair: drop; DataLad command
  685. .. _remove:
  686. Removing annexed content entirely
  687. """""""""""""""""""""""""""""""""
  688. The command to remove file content entirely and irreversibly from a repository is
  689. the :dlcmd:`drop` command.
  690. This command will delete the content stored in the annex of the dataset,
  691. and can be very helpful to make a dataset more lean if the file content is
  692. either irrelevant or can be retrieved from other sources easily. Think about a
  693. situation in which a very large result file is computed by default
  694. in some analysis, but is not relevant for any project, and can thus be removed.
  695. Or if only the results of an analysis need to be kept, but the file contents from
  696. its input datasets can be dropped at these input datasets are backed-up else
  697. where. Because the command works on annexed contents, it will drop file *content*
  698. from a dataset, but it will retain the symlink for this file (as this symlink
  699. is stored in Git).
  700. :dlcmd:`drop` can take any number of files.
  701. If an entire dataset is specified, all file content in sub-*directories* is
  702. dropped automatically, but for content in sub-*datasets* to be dropped, the
  703. ``-r/--recursive`` flag has to be included.
  704. By default, DataLad will not drop any content that does not have at least
  705. one verified remote copy that the content could be retrieved from again.
  706. It is possible to drop the downloaded image, because thanks to
  707. :dlcmd:`download-url` its original location in the web is known:
  708. .. runrecord:: _examples/DL-101-136-175
  709. :language: console
  710. :workdir: dl-101/DataLad-101
  711. :notes: to drop content entirely we use datalad drop
  712. :cast: 03_git_annex_basics
  713. $ datalad drop flowers.jpg
  714. Currently, the file content is gone, but the symlink still exist. Opening the
  715. remaining symlink will fail, but the content can be obtained easily again with
  716. :dlcmd:`get`:
  717. .. runrecord:: _examples/DL-101-136-176
  718. :language: console
  719. :workdir: dl-101/DataLad-101
  720. :notes: this will keep the symlink, but drop the content, making the dataset lean
  721. :cast: 03_git_annex_basics
  722. $ datalad get flowers.jpg
  723. If a file has no verified remote copies, DataLad will only drop its
  724. content if the user enforces it using the ``--reckless [MODE]`` option, where ``[MODE]`` is either ``modification`` (drop despite unsaved modifications), ``availability`` (drop even though no other copy is known), ``undead`` (only for datasets; would drop a dataset without announcing its death to linked dataset clones) or ``kill`` (no safety checks at all are run).
  725. We will demonstrate this by generating an empty file:
  726. .. runrecord:: _examples/DL-101-136-177
  727. :workdir: dl-101/DataLad-101
  728. :language: console
  729. :notes: the content could be dropped bc the file was obtained with datalad, and dl knows where to retrieve the file again. If this isn't the case, datalad will complain. Let's try:
  730. :cast: 03_git_annex_basics
  731. $ dd if=/dev/zero | head -c 18520 > a.pdf
  732. $ datalad save -m "add some file" a.pdf
  733. DataLad will safeguard dropping content that it cannot retrieve again:
  734. .. runrecord:: _examples/DL-101-136-178
  735. :workdir: dl-101/DataLad-101
  736. :language: console
  737. :exitcode: 1
  738. :notes: datalad does not know how to reobtain the file, so it complains
  739. :cast: 03_git_annex_basics
  740. $ datalad drop a.pdf
  741. But with ``--reckless availability`` it will work:
  742. .. runrecord:: _examples/DL-101-136-179
  743. :workdir: dl-101/DataLad-101
  744. :language: console
  745. :notes: the --nocheck/--reckless flag lets us drop content anyway. This content is gone forever now, though!
  746. :cast: 03_git_annex_basics
  747. $ datalad drop --reckless availability a.pdf
  748. Note though that this file content is irreversibly gone now, and
  749. even going back in time in the history of the dataset will not bring it
  750. back into existence.
  751. Finally, let's clean up:
  752. .. runrecord:: _examples/DL-101-136-180
  753. :workdir: dl-101/DataLad-101
  754. :language: console
  755. :notes: let's clean up
  756. :cast: 03_git_annex_basics
  757. $ git reset --hard HEAD~2
  758. Deleting content stored in Git
  759. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  760. It is much harder to delete dataset content that is stored in Git compared to
  761. content stored in git-annex.
  762. Operations such as ``rm`` or ``git rm`` remove the file from the *worktree*,
  763. but not from its history, and they can be brought back to life just as annexed
  764. contents that were solely ``rm``\'ed. There is also no straightforward
  765. Git equivalent of ``drop``.
  766. To accomplish a complete removal of a file from a dataset, we recommend the external tool
  767. `git-filter-repo <https://github.com/newren/git-filter-repo>`_.
  768. It is a powerful and potentially very dangerous tool to rewrite Git history.
  769. Usually, removing files stored in Git completely
  770. is not a common or recommended operation, as it involves quite aggressive
  771. rewriting of the dataset history. Sometimes, however, sensitive files, for example
  772. private :term:`SSH key`\s or passwords, or too many or too large files are
  773. accidentally saved into Git, and *need* to get out of the dataset history.
  774. The command ``git-filter-repo <path-specification> --force`` will "filter-out",
  775. i.e., remove all files **but the ones specified** in ``<path-specification>``
  776. from the dataset's history. An advanced chapter in the online-handbook
  777. shows an example invocation.
  778. .. index::
  779. pair: drop; DataLad command
  780. Uninstalling or deleting subdatasets
  781. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  782. Depending on the exact aim, different commands are of relevance for
  783. deleting a DataLad subdataset.
  784. One way to uninstall a dataset is the :dlcmd:`drop` command.
  785. To work on datasets, ``drop`` needs to be parametrized with ``--what all``.
  786. If needed, add ``--recursive`` in case the dataset contains subdatasets, and a
  787. fitting ``--reckless`` mode, such as ``datalad drop --what all --reckless kill --recursive``.
  788. .. runrecord:: _examples/DL-101-136-181
  789. :language: console
  790. :workdir: dl-101/DataLad-101
  791. :notes: To get rid of subdatasets one can either uninstall or remove them. let's clone one to see:
  792. :cast: 03_git_annex_basics
  793. $ # clone a subdataset - the content is irrelevant, so why not a cloud :)
  794. $ datalad clone -d . \
  795. https://github.com/datalad-datasets/disneyanimation-cloud.git \
  796. cloud
  797. To uninstall the dataset, you can use
  798. .. runrecord:: _examples/DL-101-136-182
  799. :language: console
  800. :workdir: dl-101/DataLad-101
  801. :notes: uninstall drop the dataset, but it is still registered in the superdataset. a dl install will get the dataset again!
  802. :cast: 03_git_annex_basics
  803. $ datalad drop --what all --reckless kill --recursive cloud
  804. Note that the dataset is still known in the dataset, and not completely removed.
  805. A ``datalad get [-n/--no-data] cloud`` would install the dataset again.
  806. .. index::
  807. pair: remove; DataLad command
  808. In case one wants to fully delete a subdataset from a dataset, the
  809. :dlcmd:`remove` command is relevant [#f3]_.
  810. It needs a pointer to the root of the superdataset with the ``-d/--dataset``
  811. flag, a path to the subdataset to be removed, and optionally a commit message
  812. (``-m/--message``) or recursive specification (``-r/--recursive``).
  813. To remove a subdataset, we will install the uninstalled subdataset again, and
  814. subsequently remove it with the :dlcmd:`remove` command:
  815. .. runrecord:: _examples/DL-101-136-183
  816. :language: console
  817. :workdir: dl-101/DataLad-101
  818. :notes: to completely remove the dataset, use datalad remove
  819. :cast: 03_git_annex_basics
  820. $ datalad get -n cloud
  821. .. runrecord:: _examples/DL-101-136-184
  822. :language: console
  823. :workdir: dl-101/DataLad-101
  824. :notes: to completely remove the dataset, use datalad remove
  825. :cast: 03_git_annex_basics
  826. $ # delete the subdataset
  827. $ datalad remove -m "remove obsolete subds" -d . cloud
  828. Note that for both commands a pointer to the *current directory* will not work.
  829. ``datalad remove .`` or ``datalad drop .`` will fail, even if
  830. the command is executed in a subdataset instead of the top-level
  831. superdataset -- you need to execute the command from a higher-level directory.
  832. Deleting a superdataset
  833. ^^^^^^^^^^^^^^^^^^^^^^^
  834. If for whatever reason you at one point tried to remove a DataLad dataset,
  835. whether with a GUI or the command line call ``rm -rf <directory>``, you likely
  836. have seen permission denied errors such as
  837. .. code-block:: console
  838. rm: cannot remove '<directory>/.git/annex/objects/Mz/M1/MD5E-s422982--2977b5c6ea32de1f98689bc42613aac7.jpg/MD5E-s422982--2977b5c6ea32de1f98689bc42613aac7.jpg': Permission denied
  839. rm: cannot remove '<directory>/.git/annex/objects/FP/wv/MD5E-s543180--6209797211280fc0a95196b0f781311e.jpg/MD5E-s543180--6209797211280fc0a95196b0f781311e.jpg': Permission denied
  840. [...]
  841. This error indicates that there is write-protected content within ``.git`` that
  842. cannot not be deleted. What is this write-protected content? It's the file content
  843. stored in the object tree of git-annex. If you want, you can re-read the section on
  844. :ref:`symlink` to find out how git-annex revokes write permission for the user
  845. to protect the file content given to it. To remove a dataset with annexed content
  846. one has to regain write permissions to everything in the dataset. This is done
  847. with the Unix ``chmod`` command:
  848. .. code-block:: console
  849. $ chmod -R u+w <dataset>
  850. This *recursively* (``-R``, i.e., throughout all files and (sub)directories) gives users
  851. (``u``) write permissions (``+w``) for the dataset.
  852. Afterwards, ``rm -rf <dataset>`` will succeed.
  853. However, instead of ``rm -rf``, a faster way to remove a dataset is using either :dlcmd:`drop` or :dlcmd:`remove`: Run ``datalad drop -d <dataset>`` or ``datalad remove -d <dataset>`` outside of the
  854. superdataset to remove a top-level dataset with all its contents. Likely,
  855. both ``--recursive`` and ``--reckless [availability|undead|kill]`` flags are necessary
  856. to traverse into subdatasets and to remove content that does not have verified remotes.
  857. Be aware, though, that deleting a dataset in which ever way will
  858. irretrievably delete the dataset, it's contents, and it's history.
  859. Summary
  860. ^^^^^^^
  861. To sum up, file system management operations are safe and easy.
  862. Even if you are currently confused about one or two operations,
  863. worry not -- the take-home-message is simple: Use ``datalad save``
  864. whenever you move or rename files. Be mindful that a ``datalad status``
  865. can appear unintuitive or that symlinks can break if annexed files are moved,
  866. but all of these problems are solved after a :dlcmd:`save` command.
  867. Apart from this command, having a clean dataset status prior to doing anything
  868. is your friend as well. It will make sure that you have a neat and organized
  869. commit history, and no accidental commits of changes unrelated to your file
  870. system management operations. The only operation you should beware of is
  871. moving subdatasets around -- this can be a minefield.
  872. With all of these experiences and tips, you feel confident that you know
  873. how to handle your datasets files and directories well and worry-free.
  874. .. rubric:: Footnotes
  875. .. [#f1] If you want to learn more about the Git-specific concepts of *worktree*,
  876. *staging area*/*index* or *HEAD*, the upcoming section :ref:`history` will
  877. talk briefly about them and demonstrate helpful commands.
  878. .. [#f2] Or rather: split -- basically, the file is getting a fresh new start.
  879. Think of it as some sort of witness-protection program with complete
  880. disrespect for provenance...
  881. .. [#f3] This is indeed the only case in which :dlcmd:`remove` is
  882. relevant. For all other cases of content deletion a normal ``rm``
  883. with a subsequent :dlcmd:`save` works best.