darcs-beta (empty) → 2.2.98.1
raw patch · 421 files changed
+52488/−0 lines, 421 filesdep +HTTPdep +HUnitdep +QuickCheckbuild-type:Customsetup-changedbinary-added
Dependencies added: HTTP, HUnit, QuickCheck, array, base, bytestring, containers, directory, filepath, hashed-storage, haskeline, html, mmap, mtl, network, old-time, parsec, process, random, regex-compat, terminfo, test-framework, test-framework-hunit, test-framework-quickcheck2, unix, utf8-string, zlib
This diff is very large; some files are shown as “too large to diff”. Download the raw patch for the complete diff.
Files
- COPYING +339/−0
- Distribution/ShellHarness.hs +174/−0
- README +90/−0
- Setup.lhs +389/−0
- bugs/add_permissions.sh +36/−0
- bugs/dist-v.sh +18/−0
- bugs/emailformat.sh +46/−0
- bugs/issue1013_either_dependency.sh +53/−0
- bugs/issue1014_identical_patches.sh +60/−0
- bugs/issue1190_unmarked_hunk_replace_conflict.sh +43/−0
- bugs/issue1196_whatsnew_falsely_lists_all_changes.sh +21/−0
- bugs/issue1266_init_inside_a_repo.sh +33/−0
- bugs/issue1327.sh +27/−0
- bugs/issue1337_darcs_changes_false_positives.sh +37/−0
- bugs/issue1396_changepref-conflict.sh +32/−0
- bugs/issue1401_bug_in_get_extra.sh +44/−0
- bugs/issue1406.sh +19/−0
- bugs/issue1442_encoding_round-trip.sh +50/−0
- bugs/issue1461_case_folding.sh +83/−0
- bugs/issue1473_annotate_repodir.sh +21/−0
- bugs/issue27.sh +43/−0
- bugs/issue390_whatsnew.sh +33/−0
- bugs/issue558_broken_pipe.sh +29/−0
- bugs/issue944_partial_inventory.sh +46/−0
- bugs/merging_newlines.sh +47/−0
- bugs/newlines.sh +28/−0
- bugs/nfs-failure.sh +35/−0
- bugs/record-scaling.sh +36/−0
- bugs/time-stamps.sh +52/−0
- contrib/_darcs.zsh +36/−0
- contrib/cgi/README.in +42/−0
- contrib/cgi/cgi.conf.in +31/−0
- contrib/cgi/darcs.cgi.in +491/−0
- contrib/cgi/xslt/annotate.xslt +390/−0
- contrib/cgi/xslt/browse.xslt +110/−0
- contrib/cgi/xslt/common.xslt +66/−0
- contrib/cgi/xslt/errors.xml +22/−0
- contrib/cgi/xslt/errors.xslt +36/−0
- contrib/cgi/xslt/patches.xslt +125/−0
- contrib/cgi/xslt/repos.xslt +59/−0
- contrib/cgi/xslt/rss.xslt +92/−0
- contrib/cgi/xslt/styles.css +91/−0
- contrib/cygwin-wrapper.bash +263/−0
- contrib/darcs_completion +52/−0
- contrib/update_roundup.pl +78/−0
- contrib/upload.cgi +126/−0
- darcs-beta.cabal +648/−0
- release/distributed-context +1/−0
- release/distributed-version +1/−0
- src/ByteStringUtils.hs +575/−0
- src/CommandLine.hs +122/−0
- src/Crypt/SHA256.hs +44/−0
- src/Crypt/sha2.c +950/−0
- src/Crypt/sha2.h +108/−0
- src/Darcs/ArgumentDefaults.lhs +172/−0
- src/Darcs/Arguments.lhs +1618/−0
- src/Darcs/Bug.hs +27/−0
- src/Darcs/CheckFileSystem.hs +59/−0
- src/Darcs/ColorPrinter.hs +281/−0
- src/Darcs/Commands.lhs +294/−0
- src/Darcs/Commands/Add.lhs +278/−0
- src/Darcs/Commands/AmendRecord.lhs +195/−0
- src/Darcs/Commands/Annotate.lhs +411/−0
- src/Darcs/Commands/Apply.lhs +431/−0
- src/Darcs/Commands/Changes.lhs +294/−0
- src/Darcs/Commands/Check.lhs +107/−0
- src/Darcs/Commands/Convert.lhs +260/−0
- src/Darcs/Commands/Diff.lhs +245/−0
- src/Darcs/Commands/Dist.lhs +156/−0
- src/Darcs/Commands/GZCRCs.lhs +180/−0
- src/Darcs/Commands/Get.lhs +347/−0
- src/Darcs/Commands/Help.lhs +266/−0
- src/Darcs/Commands/Init.lhs +89/−0
- src/Darcs/Commands/MarkConflicts.lhs +106/−0
- src/Darcs/Commands/Move.lhs +186/−0
- src/Darcs/Commands/Optimize.lhs +314/−0
- src/Darcs/Commands/Pull.lhs +274/−0
- src/Darcs/Commands/Push.lhs +255/−0
- src/Darcs/Commands/Put.lhs +137/−0
- src/Darcs/Commands/Record.lhs +511/−0
- src/Darcs/Commands/Remove.lhs +131/−0
- src/Darcs/Commands/Repair.lhs +70/−0
- src/Darcs/Commands/Replace.lhs +232/−0
- src/Darcs/Commands/Revert.lhs +119/−0
- src/Darcs/Commands/Rollback.lhs +160/−0
- src/Darcs/Commands/Send.lhs +444/−0
- src/Darcs/Commands/SetPref.lhs +108/−0
- src/Darcs/Commands/Show.lhs +77/−0
- src/Darcs/Commands/ShowAuthors.lhs +160/−0
- src/Darcs/Commands/ShowBug.lhs +53/−0
- src/Darcs/Commands/ShowContents.lhs +72/−0
- src/Darcs/Commands/ShowFiles.lhs +99/−0
- src/Darcs/Commands/ShowIndex.lhs +94/−0
- src/Darcs/Commands/ShowRepo.lhs +178/−0
- src/Darcs/Commands/ShowTags.lhs +80/−0
- src/Darcs/Commands/Tag.lhs +127/−0
- src/Darcs/Commands/TrackDown.lhs +149/−0
- src/Darcs/Commands/TransferMode.lhs +88/−0
- src/Darcs/Commands/Unrecord.lhs +326/−0
- src/Darcs/Commands/Unrevert.lhs +136/−0
- src/Darcs/Commands/WhatsNew.lhs +160/−0
- src/Darcs/CommandsAux.hs +112/−0
- src/Darcs/Compat.hs +123/−0
- src/Darcs/Diff.hs +375/−0
- src/Darcs/Email.hs +234/−0
- src/Darcs/External.hs +656/−0
- src/Darcs/FilePathMonad.hs +83/−0
- src/Darcs/Flags.hs +116/−0
- src/Darcs/Global.hs +158/−0
- src/Darcs/Gorsvet.hs +242/−0
- src/Darcs/Hopefully.hs +185/−0
- src/Darcs/IO.hs +249/−0
- src/Darcs/Lock.hs +333/−0
- src/Darcs/Match.lhs +568/−0
- src/Darcs/Ordered.hs +277/−0
- src/Darcs/Patch.lhs +151/−0
- src/Darcs/Patch/Apply.lhs +522/−0
- src/Darcs/Patch/Bundle.hs +211/−0
- src/Darcs/Patch/Check.hs +335/−0
- src/Darcs/Patch/Choices.hs +292/−0
- src/Darcs/Patch/Commute.lhs +879/−0
- src/Darcs/Patch/Core.lhs +173/−0
- src/Darcs/Patch/Depends.hs +498/−0
- src/Darcs/Patch/FileName.hs +158/−0
- src/Darcs/Patch/Info.hs +331/−0
- src/Darcs/Patch/Match.lhs +346/−0
- src/Darcs/Patch/MatchData.hs +28/−0
- src/Darcs/Patch/Non.hs +187/−0
- src/Darcs/Patch/Patchy.hs +230/−0
- src/Darcs/Patch/Permutations.hs +232/−0
- src/Darcs/Patch/Prim.lhs +1130/−0
- src/Darcs/Patch/Properties.lhs +357/−0
- src/Darcs/Patch/Read.hs +255/−0
- src/Darcs/Patch/ReadMonads.hs +91/−0
- src/Darcs/Patch/Real.hs +777/−0
- src/Darcs/Patch/Set.hs +50/−0
- src/Darcs/Patch/Show.lhs +89/−0
- src/Darcs/Patch/TouchesFiles.hs +90/−0
- src/Darcs/Patch/Viewing.hs +333/−0
- src/Darcs/Population.hs +144/−0
- src/Darcs/PopulationData.hs +123/−0
- src/Darcs/PrintPatch.hs +50/−0
- src/Darcs/ProgressPatches.hs +65/−0
- src/Darcs/RemoteApply.hs +58/−0
- src/Darcs/RepoPath.hs +251/−0
- src/Darcs/Repository.hs +302/−0
- src/Darcs/Repository/ApplyPatches.hs +50/−0
- src/Darcs/Repository/Cache.hs +276/−0
- src/Darcs/Repository/Checkpoint.hs +174/−0
- src/Darcs/Repository/DarcsRepo.lhs +344/−0
- src/Darcs/Repository/Format.hs +153/−0
- src/Darcs/Repository/HashedIO.hs +417/−0
- src/Darcs/Repository/HashedRepo.hs +401/−0
- src/Darcs/Repository/Internal.hs +1062/−0
- src/Darcs/Repository/InternalTypes.hs +42/−0
- src/Darcs/Repository/Motd.lhs +49/−0
- src/Darcs/Repository/Prefs.lhs +528/−0
- src/Darcs/Repository/Pristine.hs +198/−0
- src/Darcs/Repository/Repair.hs +165/−0
- src/Darcs/Resolution.lhs +240/−0
- src/Darcs/RunCommand.hs +185/−0
- src/Darcs/Sealed.hs +103/−0
- src/Darcs/SelectChanges.hs +620/−0
- src/Darcs/Show.hs +25/−0
- src/Darcs/SignalHandler.hs +132/−0
- src/Darcs/SlurpDirectory.hs +36/−0
- src/Darcs/SlurpDirectory/Internal.hs +677/−0
- src/Darcs/Test.lhs +119/−0
- src/Darcs/TheCommands.hs +106/−0
- src/Darcs/URL.hs +81/−0
- src/Darcs/Utils.hs +258/−0
- src/DateMatcher.hs +168/−0
- src/English.hs +76/−0
- src/Exec.hs +180/−0
- src/HTTP.hs +107/−0
- src/IsoDate.hs +844/−0
- src/Lcs.hs +478/−0
- src/OldDate.hs +350/−0
- src/Printer.lhs +425/−0
- src/Progress.hs +202/−0
- src/RegChars.hs +75/−0
- src/SHA1.hs +206/−0
- src/Ssh.hs +321/−0
- src/ThisVersion.hs +13/−0
- src/URL.hs +370/−0
- src/UTF8.lhs +173/−0
- src/Workaround.hs +94/−0
- src/atomic_create.c +181/−0
- src/atomic_create.h +25/−0
- src/best_practices.tex +611/−0
- src/building_darcs.tex +93/−0
- src/c_compat.c +88/−0
- src/compat.h +12/−0
- src/configuring_darcs.tex +264/−0
- src/darcs.hs +72/−0
- src/features.tex +88/−0
- src/fpstring.c +187/−0
- src/fpstring.h +16/−0
- src/gadts.h +11/−0
- src/gpl.tex +414/−0
- src/hscurl.c +305/−0
- src/hscurl.h +9/−0
- src/impossible.h +8/−0
- src/maybe_relink.c +173/−0
- src/maybe_relink.h +1/−0
- src/preproc.hs +184/−0
- src/switching.tex +147/−0
- src/umask.c +38/−0
- src/umask.h +2/−0
- src/unit.lhs +822/−0
- src/win32/CtrlC.hs +21/−0
- src/win32/System/Posix.hs +43/−0
- src/win32/System/Posix/Files.hsc +97/−0
- src/win32/System/Posix/IO.hsc +69/−0
- src/win32/send_email.c +211/−0
- src/win32/send_email.h +9/−0
- src/win32/sys/mman.h +7/−0
- src/witnesses.hs +21/−0
- tests/EXAMPLE.sh +38/−0
- tests/add-formerly-pl.sh +63/−0
- tests/add.sh +19/−0
- tests/add_in_subdir.sh +22/−0
- tests/addexitval.sh +33/−0
- tests/addmv.sh +20/−0
- tests/addrace.sh +18/−0
- tests/addrm.sh +18/−0
- tests/amend-cancelling.sh +19/−0
- tests/amend-record.sh +49/−0
- tests/annotate.sh +19/−0
- tests/apply-hunks.sh +63/−0
- tests/ask_deps.sh +55/−0
- tests/bad-format.sh +34/−0
- tests/bad-match-pattern.sh +38/−0
- tests/bad_pending_after_pull.sh +51/−0
- tests/binary.sh +29/−0
- tests/changes.sh +71/−0
- tests/changes_send_context.sh +18/−0
- tests/changes_with_move.sh +40/−0
- tests/check.sh +35/−0
- tests/checkpoint.sh +42/−0
- tests/conflict-doppleganger.sh +225/−0
- tests/conflict-fight-failure.sh +54/−0
- tests/conflict-fight.sh +78/−0
- tests/diff.sh +18/−0
- tests/directory_confusion.sh +23/−0
- tests/disable.sh +20/−0
- tests/dist.sh +15/−0
- tests/double-unrevert.sh +29/−0
- tests/example_binary.png binary
- tests/external.sh +37/−0
- tests/filepath.sh +114/−0
- tests/get.sh +18/−0
- tests/get_tag.sh +87/−0
- tests/gzcrcs.sh +39/−0
- tests/harness.sh +16/−0
- tests/hashed_inventory.sh +194/−0
- tests/haskell_policy.sh +59/−0
- tests/hidden_conflict.sh +32/−0
- tests/hidden_conflict2.sh +70/−0
- tests/ignore-this.sh +38/−0
- tests/ignoretimes.sh +29/−0
- tests/illegal_mv.sh +21/−0
- tests/impossible_unrevert.sh +33/−0
- tests/init.sh +19/−0
- tests/invalid_pending_after_mv_to_self.sh +22/−0
- tests/issue1012_unrecord_remove.sh +18/−0
- tests/issue1017_whatsnew_stack.sh +13/−0
- tests/issue1039.sh +68/−0
- tests/issue1041.sh +13/−0
- tests/issue1043_geteff_a.sh +67/−0
- tests/issue1043_geteff_b.sh +67/−0
- tests/issue1057.sh +48/−0
- tests/issue1078_symlink.sh +21/−0
- tests/issue1101.sh +28/−0
- tests/issue1105.sh +37/−0
- tests/issue1110_get_hashed.sh +10/−0
- tests/issue1110_get_old-fashioned.sh +30/−0
- tests/issue1111-pull-intersection.sh +41/−0
- tests/issue1139-diff-last.sh +31/−0
- tests/issue1139-diff-with-no-args.sh +29/−0
- tests/issue1162_add_nonexistent_slash.sh +16/−0
- tests/issue1248.sh +20/−0
- tests/issue1269_setpref_predist.sh +28/−0
- tests/issue1373_replace_token_chars.sh +49/−0
- tests/issue1446.sh +53/−0
- tests/issue1465_ortryrunning.sh +42/−0
- tests/issue154_pull_dir_not_empty.sh +48/−0
- tests/issue157_rollback_conflict.sh +44/−0
- tests/issue174_obliterate_before_a_tag.sh +23/−0
- tests/issue184_add.sh +19/−0
- tests/issue194.sh +26/−0
- tests/issue244_changes.sh +18/−0
- tests/issue257.sh +23/−0
- tests/issue279_get_extra.sh +42/−0
- tests/issue381.sh +50/−0
- tests/issue436.sh +30/−0
- tests/issue458.sh +28/−0
- tests/issue525_amend_duplicates.sh +24/−0
- tests/issue53.sh +27/−0
- tests/issue538.sh +117/−0
- tests/issue588.sh +32/−0
- tests/issue595_get_permissions.sh +50/−0
- tests/issue612_repo_not_writeable.sh +42/−0
- tests/issue691.sh +12/−0
- tests/issue706.sh +22/−0
- tests/issue709_pending_look-for-adds.sh +56/−0
- tests/issue70_setpref.sh +62/−0
- tests/issue803.sh +31/−0
- tests/issue844_gzip_crc.sh +20/−0
- tests/issue864_replace_in_moved.sh +57/−0
- tests/issue885_get_to_match.sh +34/−0
- tests/issue966_diff.sh +25/−0
- tests/justrm.sh +15/−0
- tests/lazy-optimize-reorder.sh +64/−0
- tests/lib +18/−0
- tests/look_for_add.sh +37/−0
- tests/mark-conflicts.sh +38/−0
- tests/match-date.sh +196/−0
- tests/match.sh +97/−0
- tests/merge_three_patches.sh +52/−0
- tests/mergeresolved.sh +57/−0
- tests/merging_newlines.sh +40/−0
- tests/mv-formerly-pl.sh +136/−0
- tests/mv-test-suite.sh +28/−0
- tests/mv.sh +53/−0
- tests/mv_and_remove_tests.sh +50/−0
- tests/mv_then_add.sh +25/−0
- tests/network/changes.sh +5/−0
- tests/network/get.sh +19/−0
- tests/network/lazy-get.sh +22/−0
- tests/nodeps.sh +81/−0
- tests/nonewline.sh +21/−0
- tests/obliterate-add.sh +19/−0
- tests/obliterate-formerly-pl.sh +32/−0
- tests/obliterate.sh +51/−0
- tests/optimize.sh +16/−0
- tests/optimize_relink.sh +59/−0
- tests/output.sh +67/−0
- tests/overriding-defaults.sh +61/−0
- tests/partial.sh +60/−0
- tests/pending.sh +37/−0
- tests/pending_has_conflicts.sh +62/−0
- tests/perms.sh +31/−0
- tests/posthook.sh +26/−0
- tests/prefs.sh +16/−0
- tests/prefs_binary.sh +23/−0
- tests/prehook.sh +27/−0
- tests/printer.sh +83/−0
- tests/pull-dont-prompt-deps.sh +31/−0
- tests/pull-union.sh +42/−0
- tests/pull.sh +154/−0
- tests/pull_binary.sh +27/−0
- tests/pull_compl.sh +121/−0
- tests/pull_many_files.sh +27/−0
- tests/pull_two.sh +34/−0
- tests/push-dont-prompt-deps.sh +30/−0
- tests/push-dry-run.sh +28/−0
- tests/push-formerly-pl.sh +49/−0
- tests/push.sh +21/−0
- tests/push_lock.sh +26/−0
- tests/put.sh +31/−0
- tests/query_manifest.sh +90/−0
- tests/record-interactive.sh +27/−0
- tests/record.sh +53/−0
- tests/record_editor.sh +59/−0
- tests/recordrace.sh +21/−0
- tests/repair-clean.sh +25/−0
- tests/repair-corrupt.sh +53/−0
- tests/repair.sh +30/−0
- tests/replace.sh +61/−0
- tests/replace_after_pending_add.sh +21/−0
- tests/replace_after_pending_mv.sh +21/−0
- tests/repodir.sh +28/−0
- tests/repoformat.sh +14/−0
- tests/repos/README +4/−0
- tests/repos/many-files--darcs-2.tgz binary
- tests/repos/many-files--hashed.tgz binary
- tests/repos/many-files--old-fashioned-inventory.tgz binary
- tests/repos/maybench-crc.tgz binary
- tests/revert_interactive.sh +67/−0
- tests/revert_unrecorded_add.sh +13/−0
- tests/rmconflict.sh +24/−0
- tests/rmdir-formerly-pl.sh +32/−0
- tests/rmdir.sh +71/−0
- tests/rollback-nothing.sh +16/−0
- tests/rollback.sh +34/−0
- tests/sametwice.sh +30/−0
- tests/send-dont-prompt-deps.sh +30/−0
- tests/send-external.sh +54/−0
- tests/send.sh +52/−0
- tests/send_apply.sh +38/−0
- tests/set_scripts_executable.sh +68/−0
- tests/setpref.sh +24/−0
- tests/several_commands.sh +25/−0
- tests/show-authors.sh +30/−0
- tests/show-bug.sh +30/−0
- tests/show_contents.sh +26/−0
- tests/show_tags.sh +35/−0
- tests/sigPIPE.sh +34/−0
- tests/ssh.sh +143/−0
- tests/steve_and_monica_rollback.sh too large to diff
- tests/tag.sh too large to diff
- tests/three_way_conflict.sh too large to diff
- tests/toolbox.sh too large to diff
- tests/tricky_unrecord.sh too large to diff
- tests/uniqueoptions.sh too large to diff
- tests/unpull-formerly-pl.sh too large to diff
- tests/unpull.sh too large to diff
- tests/unrecord-add.sh too large to diff
- tests/unrecord-dont-prompt.sh too large to diff
- tests/unrecord-remove.sh too large to diff
- tests/unrecord-setpref.sh too large to diff
- tests/unrecord.sh too large to diff
- tests/unrevert-add.sh too large to diff
- tests/unrevert-replace-moved.sh too large to diff
- tests/unrevert.sh too large to diff
- tests/unrevert_cancel.sh too large to diff
- tests/what_sl.sh too large to diff
- tests/whatsnew-file.sh too large to diff
- tests/whatsnew.sh too large to diff
- tests/workingdir.sh too large to diff
@@ -0,0 +1,339 @@+ GNU GENERAL PUBLIC LICENSE+ Version 2, June 1991++ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA+ Everyone is permitted to copy and distribute verbatim copies+ of this license document, but changing it is not allowed.++ Preamble++ The licenses for most software are designed to take away your+freedom to share and change it. By contrast, the GNU General Public+License is intended to guarantee your freedom to share and change free+software--to make sure the software is free for all its users. This+General Public License applies to most of the Free Software+Foundation's software and to any other program whose authors commit to+using it. (Some other Free Software Foundation software is covered by+the GNU Lesser General Public License instead.) You can apply it to+your programs, too.++ When we speak of free software, we are referring to freedom, not+price. Our General Public Licenses are designed to make sure that you+have the freedom to distribute copies of free software (and charge for+this service if you wish), that you receive source code or can get it+if you want it, that you can change the software or use pieces of it+in new free programs; and that you know you can do these things.++ To protect your rights, we need to make restrictions that forbid+anyone to deny you these rights or to ask you to surrender the rights.+These restrictions translate to certain responsibilities for you if you+distribute copies of the software, or if you modify it.++ For example, if you distribute copies of such a program, whether+gratis or for a fee, you must give the recipients all the rights that+you have. You must make sure that they, too, receive or can get the+source code. And you must show them these terms so they know their+rights.++ We protect your rights with two steps: (1) copyright the software, and+(2) offer you this license which gives you legal permission to copy,+distribute and/or modify the software.++ Also, for each author's protection and ours, we want to make certain+that everyone understands that there is no warranty for this free+software. If the software is modified by someone else and passed on, we+want its recipients to know that what they have is not the original, so+that any problems introduced by others will not reflect on the original+authors' reputations.++ Finally, any free program is threatened constantly by software+patents. We wish to avoid the danger that redistributors of a free+program will individually obtain patent licenses, in effect making the+program proprietary. To prevent this, we have made it clear that any+patent must be licensed for everyone's free use or not licensed at all.++ The precise terms and conditions for copying, distribution and+modification follow.++ GNU GENERAL PUBLIC LICENSE+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION++ 0. This License applies to any program or other work which contains+a notice placed by the copyright holder saying it may be distributed+under the terms of this General Public License. The "Program", below,+refers to any such program or work, and a "work based on the Program"+means either the Program or any derivative work under copyright law:+that is to say, a work containing the Program or a portion of it,+either verbatim or with modifications and/or translated into another+language. (Hereinafter, translation is included without limitation in+the term "modification".) Each licensee is addressed as "you".++Activities other than copying, distribution and modification are not+covered by this License; they are outside its scope. The act of+running the Program is not restricted, and the output from the Program+is covered only if its contents constitute a work based on the+Program (independent of having been made by running the Program).+Whether that is true depends on what the Program does.++ 1. You may copy and distribute verbatim copies of the Program's+source code as you receive it, in any medium, provided that you+conspicuously and appropriately publish on each copy an appropriate+copyright notice and disclaimer of warranty; keep intact all the+notices that refer to this License and to the absence of any warranty;+and give any other recipients of the Program a copy of this License+along with the Program.++You may charge a fee for the physical act of transferring a copy, and+you may at your option offer warranty protection in exchange for a fee.++ 2. You may modify your copy or copies of the Program or any portion+of it, thus forming a work based on the Program, and copy and+distribute such modifications or work under the terms of Section 1+above, provided that you also meet all of these conditions:++ a) You must cause the modified files to carry prominent notices+ stating that you changed the files and the date of any change.++ b) You must cause any work that you distribute or publish, that in+ whole or in part contains or is derived from the Program or any+ part thereof, to be licensed as a whole at no charge to all third+ parties under the terms of this License.++ c) If the modified program normally reads commands interactively+ when run, you must cause it, when started running for such+ interactive use in the most ordinary way, to print or display an+ announcement including an appropriate copyright notice and a+ notice that there is no warranty (or else, saying that you provide+ a warranty) and that users may redistribute the program under+ these conditions, and telling the user how to view a copy of this+ License. (Exception: if the Program itself is interactive but+ does not normally print such an announcement, your work based on+ the Program is not required to print an announcement.)++These requirements apply to the modified work as a whole. If+identifiable sections of that work are not derived from the Program,+and can be reasonably considered independent and separate works in+themselves, then this License, and its terms, do not apply to those+sections when you distribute them as separate works. But when you+distribute the same sections as part of a whole which is a work based+on the Program, the distribution of the whole must be on the terms of+this License, whose permissions for other licensees extend to the+entire whole, and thus to each and every part regardless of who wrote it.++Thus, it is not the intent of this section to claim rights or contest+your rights to work written entirely by you; rather, the intent is to+exercise the right to control the distribution of derivative or+collective works based on the Program.++In addition, mere aggregation of another work not based on the Program+with the Program (or with a work based on the Program) on a volume of+a storage or distribution medium does not bring the other work under+the scope of this License.++ 3. You may copy and distribute the Program (or a work based on it,+under Section 2) in object code or executable form under the terms of+Sections 1 and 2 above provided that you also do one of the following:++ a) Accompany it with the complete corresponding machine-readable+ source code, which must be distributed under the terms of Sections+ 1 and 2 above on a medium customarily used for software interchange; or,++ b) Accompany it with a written offer, valid for at least three+ years, to give any third party, for a charge no more than your+ cost of physically performing source distribution, a complete+ machine-readable copy of the corresponding source code, to be+ distributed under the terms of Sections 1 and 2 above on a medium+ customarily used for software interchange; or,++ c) Accompany it with the information you received as to the offer+ to distribute corresponding source code. (This alternative is+ allowed only for noncommercial distribution and only if you+ received the program in object code or executable form with such+ an offer, in accord with Subsection b above.)++The source code for a work means the preferred form of the work for+making modifications to it. For an executable work, complete source+code means all the source code for all modules it contains, plus any+associated interface definition files, plus the scripts used to+control compilation and installation of the executable. However, as a+special exception, the source code distributed need not include+anything that is normally distributed (in either source or binary+form) with the major components (compiler, kernel, and so on) of the+operating system on which the executable runs, unless that component+itself accompanies the executable.++If distribution of executable or object code is made by offering+access to copy from a designated place, then offering equivalent+access to copy the source code from the same place counts as+distribution of the source code, even though third parties are not+compelled to copy the source along with the object code.++ 4. You may not copy, modify, sublicense, or distribute the Program+except as expressly provided under this License. Any attempt+otherwise to copy, modify, sublicense or distribute the Program is+void, and will automatically terminate your rights under this License.+However, parties who have received copies, or rights, from you under+this License will not have their licenses terminated so long as such+parties remain in full compliance.++ 5. You are not required to accept this License, since you have not+signed it. However, nothing else grants you permission to modify or+distribute the Program or its derivative works. These actions are+prohibited by law if you do not accept this License. Therefore, by+modifying or distributing the Program (or any work based on the+Program), you indicate your acceptance of this License to do so, and+all its terms and conditions for copying, distributing or modifying+the Program or works based on it.++ 6. Each time you redistribute the Program (or any work based on the+Program), the recipient automatically receives a license from the+original licensor to copy, distribute or modify the Program subject to+these terms and conditions. You may not impose any further+restrictions on the recipients' exercise of the rights granted herein.+You are not responsible for enforcing compliance by third parties to+this License.++ 7. If, as a consequence of a court judgment or allegation of patent+infringement or for any other reason (not limited to patent issues),+conditions are imposed on you (whether by court order, agreement or+otherwise) that contradict the conditions of this License, they do not+excuse you from the conditions of this License. If you cannot+distribute so as to satisfy simultaneously your obligations under this+License and any other pertinent obligations, then as a consequence you+may not distribute the Program at all. For example, if a patent+license would not permit royalty-free redistribution of the Program by+all those who receive copies directly or indirectly through you, then+the only way you could satisfy both it and this License would be to+refrain entirely from distribution of the Program.++If any portion of this section is held invalid or unenforceable under+any particular circumstance, the balance of the section is intended to+apply and the section as a whole is intended to apply in other+circumstances.++It is not the purpose of this section to induce you to infringe any+patents or other property right claims or to contest validity of any+such claims; this section has the sole purpose of protecting the+integrity of the free software distribution system, which is+implemented by public license practices. Many people have made+generous contributions to the wide range of software distributed+through that system in reliance on consistent application of that+system; it is up to the author/donor to decide if he or she is willing+to distribute software through any other system and a licensee cannot+impose that choice.++This section is intended to make thoroughly clear what is believed to+be a consequence of the rest of this License.++ 8. If the distribution and/or use of the Program is restricted in+certain countries either by patents or by copyrighted interfaces, the+original copyright holder who places the Program under this License+may add an explicit geographical distribution limitation excluding+those countries, so that distribution is permitted only in or among+countries not thus excluded. In such case, this License incorporates+the limitation as if written in the body of this License.++ 9. The Free Software Foundation may publish revised and/or new versions+of the General Public License from time to time. Such new versions will+be similar in spirit to the present version, but may differ in detail to+address new problems or concerns.++Each version is given a distinguishing version number. If the Program+specifies a version number of this License which applies to it and "any+later version", you have the option of following the terms and conditions+either of that version or of any later version published by the Free+Software Foundation. If the Program does not specify a version number of+this License, you may choose any version ever published by the Free Software+Foundation.++ 10. If you wish to incorporate parts of the Program into other free+programs whose distribution conditions are different, write to the author+to ask for permission. For software which is copyrighted by the Free+Software Foundation, write to the Free Software Foundation; we sometimes+make exceptions for this. Our decision will be guided by the two goals+of preserving the free status of all derivatives of our free software and+of promoting the sharing and reuse of software generally.++ NO WARRANTY++ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,+REPAIR OR CORRECTION.++ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE+POSSIBILITY OF SUCH DAMAGES.++ END OF TERMS AND CONDITIONS++ How to Apply These Terms to Your New Programs++ If you develop a new program, and you want it to be of the greatest+possible use to the public, the best way to achieve this is to make it+free software which everyone can redistribute and change under these terms.++ To do so, attach the following notices to the program. It is safest+to attach them to the start of each source file to most effectively+convey the exclusion of warranty; and each file should have at least+the "copyright" line and a pointer to where the full notice is found.++ <one line to give the program's name and a brief idea of what it does.>+ Copyright (C) <year> <name of author>++ This program is free software; you can redistribute it and/or modify+ it under the terms of the GNU General Public License as published by+ the Free Software Foundation; either version 2 of the License, or+ (at your option) any later version.++ This program is distributed in the hope that it will be useful,+ but WITHOUT ANY WARRANTY; without even the implied warranty of+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+ GNU General Public License for more details.++ You should have received a copy of the GNU General Public License along+ with this program; if not, write to the Free Software Foundation, Inc.,+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.++Also add information on how to contact you by electronic and paper mail.++If the program is interactive, make it output a short notice like this+when it starts in an interactive mode:++ Gnomovision version 69, Copyright (C) year name of author+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.+ This is free software, and you are welcome to redistribute it+ under certain conditions; type `show c' for details.++The hypothetical commands `show w' and `show c' should show the appropriate+parts of the General Public License. Of course, the commands you use may+be called something other than `show w' and `show c'; they could even be+mouse-clicks or menu items--whatever suits your program.++You should also get your employer (if you work as a programmer) or your+school, if any, to sign a "copyright disclaimer" for the program, if+necessary. Here is a sample; alter the names:++ Yoyodyne, Inc., hereby disclaims all copyright interest in the program+ `Gnomovision' (which makes passes at compilers) written by James Hacker.++ <signature of Ty Coon>, 1 April 1989+ Ty Coon, President of Vice++This General Public License does not permit incorporating your program into+proprietary programs. If your program is a subroutine library, you may+consider it more useful to permit linking proprietary applications with the+library. If this is what you want to do, use the GNU Lesser General+Public License instead of this License.
@@ -0,0 +1,174 @@+{-# OPTIONS_GHC -cpp #-}+module Distribution.ShellHarness ( runTests ) where++import Prelude hiding( catch )+import System.Directory ( getCurrentDirectory, setPermissions,+ Permissions(..), getDirectoryContents,+ findExecutable, createDirectoryIfMissing )+import System.Environment ( getEnv, getEnvironment )+import System.Exit ( ExitCode (..) )+import System.FilePath+import System.IO+import System.Process ( ProcessHandle,+ runInteractiveProcess, waitForProcess,+ getProcessExitCode )+import Data.Maybe+import Data.List ( isInfixOf, isPrefixOf, (\\), nubBy )+import Control.Concurrent+#if __GLASGOW_HASKELL__ >= 610+import Control.OldException+#else+import Control.Exception+#endif+import Control.Monad++runTests :: Maybe FilePath -> String -> [String] -> IO Bool+runTests darcs_path cwd tests = do+ fails <- run darcs_path tests+ if "bugs" `isInfixOf` cwd+ then if (length tests /= length fails)+ then do putStrLn $ "Some bug tests passed:"+ mapM_ putStrLn (tests \\ fails)+ return False+ else do putStrLn "All bug tests OK"+ return True+ else if fails /= []+ then do putStrLn "Some tests failed:"+ mapM_ putStrLn fails+ return False+ else do putStrLn "All tests OK"+ return True++run :: Maybe FilePath -> [String] -> IO [String]+run set_darcs_path tests= do+ cwd <- getCurrentDirectory+ path <- getEnv "PATH"+ env <- getEnvironment+ darcs_path <- case set_darcs_path of+ Nothing -> get_darcs_path+ Just x -> return x+ let myenv = [("HOME",cwd)+ ,("APPDATA",cwd) -- the Windows analogue of $HOME.+ ,("TESTS_WD",cwd)+ ,("EMAIL","tester")+ ,("DARCSEMAIL","tester")+ ,("PATH",(darcs_path++":"++path))+ ,("DARCS_DONT_COLOR","1")+ ,("DARCS_DONT_ESCAPE_ANYTHING","1")]+ bash <- find_bash+ let shell = takeWhile (/= '\n') bash+ putStrLn $ "Using bash shell in '"++shell++"'"+ set_prefs+ run_helper shell tests [] (set_env myenv env)+ where get_darcs_path = do+ env <- getEnvironment+ cwd <- getCurrentDirectory+ case lookup "DARCS" env of+ Nothing -> return (cwd ++ "/..")+ Just d -> return $ takeDirectory d+ set_prefs = do+ finally (catch (appendFile ".darcs/defaults" "\nALL --ignore-times\n")+ (\e -> fail $ "Unable to set preferences: "+ ++ show e))+ (createDirectoryIfMissing False ".darcs")+run_helper :: String -> [String] -> [String] ->+ [(String,String)] -> IO [String]+run_helper _ [] fails _ = return fails+run_helper shell (test:ts) fails env = do+ putStr $ "Running " ++ test ++ " ..." ++ (replicate (36 - (length test)) ' ')+ (output,success) <- backtick shell test env+ if success then do putStrLn " passed."+ cleanup_dirs+ run_helper shell ts fails env+ else do putStrLn " failed."+ putStrLn $ "Probable reason :" ++ output+ cleanup_dirs+ run_helper shell ts (fails++[test]) env+ where cleanup_dirs :: IO ()+ cleanup_dirs =+ do dirfiles <- getDirectoryContents (fromJust $ lookup "TESTS_WD" env)+ let tempfiles = (filter ("temp" `isPrefixOf`) dirfiles) +++ (filter ("tmp" `isPrefixOf`) dirfiles)+ mapM_ (\x-> + setPermissions x (Permissions + {readable = True+ ,writable = True+ ,executable = False+ ,searchable = True}+ )+ ) tempfiles++backtick :: String -> String -> [(String, String)]-> IO (String,Bool)+backtick cmd args env = do+ (exitcode,res) <- backtick_helper cmd args env+ case exitcode of+ ExitSuccess -> return (res, True)+ ExitFailure _ -> return (res, False)++backtick_helper :: String -> String -> [(String,String)] ->+ IO (ExitCode, String)+backtick_helper cmd args env = process_wrapper (runInteractiveProcess+ cmd [args] Nothing+ (Just env)+ ) ""++find_bash :: IO FilePath+find_bash =+ do sh <- findExecutable "bash"+ case sh of+ Just p -> return p+ Nothing -> error "Could not find bash in PATH"++-- | Run a process with a list of arguments and return anything from+-- /stderr/ or /stdout/+process_wrapper :: IO (Handle, Handle, Handle, ProcessHandle) ->+ String -> IO (ExitCode, String)+process_wrapper f _ = do+ (_,o,e,pid) <- f+ hSetBuffering o LineBuffering+ hSetBuffering e LineBuffering+ ch <- newChan+ -- WARNING: beware of hokeyness ahead!+ let readWrite i = do x <- hGetLine i+ writeChan ch $ Just x+ readWrite i+ `catch` \_ -> writeChan ch Nothing+ readEO = do x <- readChan ch+ case x of+ Just l -> do y <- readEO+ return $ l:y+ Nothing -> readEO'+ readEO' = do x <- readChan ch+ case x of+ Just l -> do y <- readEO'+ return $ l:y+ Nothing -> return []+ forkIO $ readWrite o+ forkIO $ readWrite e+ outerr <- readEO+ ec <- waitForProcessNonBlocking pid+ threadDelay 1000+ case ec of+ ExitFailure 127 -> fail $ "timeout running command\n\n"+ ++unlines outerr+ _ -> return (ec, unlines outerr)++--+-- waitForProcess uses a very hokey heuristic to try to avoid burning too+-- much CPU time in a busy wait, while also not adding too much extra+-- latency.++waitForProcessNonBlocking :: ProcessHandle -> IO ExitCode+waitForProcessNonBlocking = if rtsSupportsBoundThreads+ then waitForProcess+ else wfp 0+ where wfp n pid = do mec <- getProcessExitCode pid+ case mec of+ Just ec -> return ec+ Nothing -> do threadDelay n+ wfp (min 100000 (n+1+n`div`4)) pid++++set_env :: [(String,String)] -> [(String,String)] -> [(String, String)]+set_env es env = nubBy (\(x,_) (y,_) -> x == y) (es ++ env)
@@ -0,0 +1,90 @@+Darcs+=====+This is the source tree for darcs, a distributed version control system+written in Haskell.++For more information, visit the darcs web site:++ http://www.darcs.net+++Compilation and Installation+============================++Darcs currently supports two build systems: a traditional autotools-+based system, and an experimental cabal-based system. Currently+AUTOTOOLS IS RECOMMENDED; in later releases it will be deprecated and+eventually removed (in favour of cabal).++If a "configure" file is present, autotools is supported. If a+"Setup.lhs" file is present, cabal is supported. Currently there is a+separate source tarball for each build system; either can be used in+unstable checkouts.+++Using Autotools+---------------++If you have the normal, autotools-based tarball, this sequence should work+for you (if you are in doubt, this is likely the case):++ $ ./configure+ $ make+ # make install++You first need to run `autoconf` if you obtained the source tree from the+darcs repository (but this is not needed for release tarballs).++For more information, please see the manual:++ * http://www.darcs.net/manual+ * doc/manual/darcs.ps+++Using Cabal+-----------++This method requires the cabal package, version 1.6 or higher. The+cabal-install package is also recommended.++If you have the "cabal-install" package on your system (that is, there+is a "cabal" executable in your path), you can use the following+commands to create an executable in ~/.cabal/bin/darcs.++ $ cabal configure+ $ cabal build+ $ cabal install++Otherwise, if you have the "cabal" package but not the "cabal-install"+package, run the following:++ $ runghc Setup configure+ $ runghc Setup build+ $ sudo runghc Setup install++You may also omit the last step and copy the darcs executable (found in+dist/build/darcs/darcs) to a location of your choosing.++More detailed instructions can be found at+<http://www.haskell.org/haskellwiki/Cabal/How_to_install_a_Cabal_package>+including instructions on obtaining a copy of cabal-install.++Please also note that the cabal-based build by default requires the cURL+library (and development headers). If, for some reason, you cannot provide+cURL, please pass "-f-curl" to the configure step above.++Hacking+=======+For more information about darcs hacking and best practices please check+the darcs wiki at http://wiki.darcs.net/DarcsWiki++Of particular interest are the following documents:+ * http://wiki.darcs.net/index.html/DeveloperFAQ+ * http://wiki.darcs.net/index.html/DeveloperTips++Testing+=======+For more information about the test suite, including how to run specific+tests please read tests/README.test_maintainers.txt.++Happy hacking!
@@ -0,0 +1,389 @@+\begin{code}+{-# OPTIONS_GHC -cpp #-}+-- copyright (c) 2008 Duncan Coutts+-- portions copyright (c) 2008 David Roundy++import Distribution.Simple+ ( defaultMainWithHooks, UserHooks(..), simpleUserHooks )+import Distribution.PackageDescription+ ( PackageDescription(executables), Executable(buildInfo, exeName)+ , BuildInfo(customFieldsBI), emptyBuildInfo+ , updatePackageDescription, cppOptions, ccOptions )+import Distribution.Package+ ( packageVersion )+import Distribution.Version+ ( Version(versionBranch) )+import Distribution.Simple.LocalBuildInfo+ ( LocalBuildInfo(..), absoluteInstallDirs )+import Distribution.Simple.InstallDirs (mandir, CopyDest (NoCopyDest))+import Distribution.Simple.Setup+ (buildVerbosity, copyDest, copyVerbosity, fromFlag,+ haddockVerbosity, installVerbosity, sDistVerbosity)+import Distribution.Simple.BuildPaths+ ( autogenModulesDir )+import Distribution.System+ ( OS(Windows), buildOS )+import Distribution.Simple.Utils+ (copyFiles, createDirectoryIfMissingVerbose, rawSystemStdout,+ rewriteFile)+import Distribution.Verbosity+ ( Verbosity )+import Distribution.Text+ ( display )+import Distribution.Package (Package)++import Control.Monad ( zipWithM_, when, unless, filterM )+import Control.Exception ( bracket )+import System.Directory+ (copyFile, createDirectory, createDirectoryIfMissing,+ doesDirectoryExist, doesFileExist,+ getCurrentDirectory, getDirectoryContents,+ removeDirectoryRecursive, removeFile, setCurrentDirectory)+import System.IO (openFile, IOMode (..))+import System.Process (runProcess)+import System.IO.Error ( isDoesNotExistError )+import Data.List( isSuffixOf, sort, partition )++import System.FilePath ( (</>), splitDirectories, isAbsolute )+import Foreign.Marshal.Utils ( with )+import Foreign.Storable ( peek )+import Foreign.Ptr ( castPtr )+import Data.Word ( Word8, Word32 )++import qualified Distribution.ShellHarness as Harness ( runTests )++#if __GLASGOW_HASKELL__ >= 610+import qualified Control.OldException as Exception+#else+import qualified Control.Exception as Exception+#endif++main :: IO ()+main = defaultMainWithHooks simpleUserHooks {++ buildHook = \ pkg lbi hooks flags ->+ let verb = fromFlag $ buildVerbosity flags+ in commonBuildHook buildHook pkg lbi hooks verb >>= ($ flags),++ haddockHook = \ pkg lbi hooks flags ->+ let verb = fromFlag $ haddockVerbosity flags+ in commonBuildHook haddockHook pkg lbi hooks verb >>= ($ flags) ,++ postBuild = \ _ _ _ lbi -> buildManpage lbi,+ postCopy = \ _ flags pkg lbi ->+ installManpage pkg lbi (fromFlag $ copyVerbosity flags) (fromFlag $ copyDest flags),+ postInst = \ _ flags pkg lbi ->+ installManpage pkg lbi (fromFlag $ installVerbosity flags) NoCopyDest,++ runTests = \ args _ _ lbi -> do+ cwd <- getCurrentDirectory+ let isabs = isAbsolute $ buildDir lbi+ path = (if isabs then id else (cwd </>))+ (buildDir lbi </> "darcs")+ what = if null args then ["tests"] else args+ (series, tests) = partition+ (`elem` ["bugs", "network", "tests"]) what+ sequence_ [ case w of+ "bugs" -> allTests path Bug []+ "network" -> execTests path Network "" []+ "tests" -> allTests path Test []+ _ -> return () {- impossible, silence -Wall -}+ | w <- series ]+ when (not $ null tests) $ individualTests path tests,++ -- Remove the temporary directories created by "cabal test".+ postClean = \ _ _ _ _ -> mapM_ rmRf+ ["tests-darcs-2.dir",+ "tests-hashed.dir",+ "tests-old-fashioned-inventory.dir",+ "bugs-darcs-2.dir",+ "bugs-hashed.dir",+ "bugs-old-fashioned-inventory.dir",+ "tests_network-.dir"],++ sDistHook = \ pkg lbi hooks flags -> do+ let pkgVer = packageVersion pkg+ verb = fromFlag $ sDistVerbosity flags+ x <- versionPatches verb pkgVer+ y <- context verb pkgVer+ rewriteFile "release/distributed-version" $ show x+ rewriteFile "release/distributed-context" $ show y++ sDistHook simpleUserHooks pkg lbi hooks flags+}++-- | For @./Setup build@ and @./Setup haddock@, do some unusual+-- things, then invoke the base behaviour ("simple hook").+commonBuildHook :: (UserHooks -> PackageDescription -> LocalBuildInfo -> t -> a)+ -> PackageDescription -> LocalBuildInfo -> t -> Verbosity -> IO a+commonBuildHook runHook pkg lbi hooks verbosity = do+ -- Autoconf may have generated a context file. Remove it before+ -- building, as its existence inexplicably breaks Cabal.+ removeFile "src/Context.hs"+ `catch` (\e -> unless (isDoesNotExistError e) (ioError e))++ -- Create our own context file.+ writeGeneratedModules verbosity pkg lbi++ -- Add custom -DFOO[=BAR] flags to the cpp (for .hs) and cc (for .c)+ -- invocations, doing a dance to make the base hook aware of them.+ (version, state) <- determineVersion verbosity pkg+ littleEndian <- testEndianness+ let args = ("-DPACKAGE_VERSION=" ++ show' version) :+ ("-DPACKAGE_VERSION_STATE=" ++ show' state) :+ [arg | (arg, True) <- -- include fst iff snd.+ [("-DHAVE_HTTP", "x-have-http" `elem` customFields),+ ("-DUSE_COLOR", "x-use-color" `elem` customFields),+ -- We have MAPI iff building on/for Windows.+ ("-DHAVE_MAPI", buildOS == Windows),+ ("-DBIGENDIAN", not littleEndian)]]+ bi = emptyBuildInfo { cppOptions = args, ccOptions = args }+ hbi = (Just bi, [(exeName exe, bi) | exe <- executables pkg])+ pkg' = updatePackageDescription hbi pkg+ lbi' = lbi { localPkgDescr = pkg' }+ return $ runHook simpleUserHooks pkg' lbi' hooks++ where+ customFields = map fst . customFieldsBI . buildInfo $ darcsExe+ darcsExe = head [e | e <- executables pkg, exeName e == "darcs"]+ show' :: String -> String -- Petr was worried that we might+ show' = show -- allow non-String arguments.+ testEndianness :: IO Bool+ testEndianness = with (1 :: Word32) $ \p -> do o <- peek $ castPtr p+ return $ o == (1 :: Word8)++buildManpage :: LocalBuildInfo -> IO ()+buildManpage lbi = do+ let darcs = buildDir lbi </> "darcs/darcs"+ manpage = buildDir lbi </> "darcs/darcs.1"+ manpageHandle <- openFile manpage WriteMode+ runProcess darcs ["help","manpage"]+ Nothing Nothing Nothing (Just manpageHandle) Nothing+ return ()++installManpage :: PackageDescription -> LocalBuildInfo+ -> Verbosity -> CopyDest -> IO ()+installManpage pkg lbi verbosity copy =+ copyFiles verbosity+ (mandir (absoluteInstallDirs pkg lbi copy) </> "man1")+ [(buildDir lbi </> "darcs", "darcs.1")]++writeGeneratedModules :: Verbosity+ -> PackageDescription -> LocalBuildInfo -> IO ()+writeGeneratedModules verbosity pkg lbi = do+ createDirectoryIfMissingVerbose verbosity True (autogenModulesDir lbi)++ let contextModulePath = autogenModulesDir lbi </> "Context.hs"+ generateContextModule verbosity contextModulePath pkg++determineVersion :: Verbosity -> PackageDescription -> IO (String, String)+determineVersion verbosity pkg = do+ let darcsVersion = packageVersion pkg+ numPatches <- versionPatches verbosity darcsVersion+ return (display darcsVersion, versionStateString numPatches darcsVersion)++ where+ versionStateString :: Maybe Int -> Version -> String+ versionStateString Nothing _ = "unknown"+ versionStateString (Just 0) v = case versionBranch v of+ x | 97 `elem` x -> "alpha " ++ show (after 97 x)+ | 98 `elem` x -> "beta " ++ show (after 98 x)+ | 99 `elem` x ->+ "release candidate " ++ show (after 99 x)+ _ -> "release"+ versionStateString (Just 1) _ = "+ 1 patch"+ versionStateString (Just n) _ = "+ " ++ show n ++ " patches"+ after w (x:r) | w == x = head r+ | otherwise = after w r+ after _ [] = undefined++versionPatches :: Verbosity -> Version -> IO (Maybe Int)+versionPatches verbosity darcsVersion = do+ numPatchesDarcs <- do+ out <- rawSystemStdout verbosity "darcs"+ ["changes", "--from-tag", display darcsVersion, "--count"]+ case reads (out) of+ ((n,_):_) -> return $ Just ((n :: Int) - 1)+ _ -> return Nothing+ `Exception.catch` \_ -> return Nothing++ numPatchesDist <- parseFile versionFile+ return $ case (numPatchesDarcs, numPatchesDist) of+ (Just x, _) -> Just x+ (Nothing, Just x) -> Just x+ (Nothing, Nothing) -> Nothing++ where+ versionFile = "release/distributed-version"++generateContextModule :: (Package pkg) => Verbosity -> FilePath -> pkg -> IO ()+generateContextModule verbosity targetFile pkg = do+ ctx <- context verbosity (packageVersion pkg)+ rewriteFile targetFile $ unlines+ ["module Context where"+ ,"context :: String"+ ,"context = " ++ case ctx of+ Just x -> show x+ Nothing -> show "context not available"+ ]++context :: Verbosity -> Version -> IO (Maybe String)+context verbosity version = do+ contextDarcs <- do+ -- FIXME currently we run changes --from-tag to at least assert that the+ -- requested version is tagged in this repository... it is a weak check,+ -- but otherwise, my ~/_darcs context tends to gets used when running+ -- from an unpacked distribution+ rawSystemStdout verbosity "darcs"+ ["changes", "--from-tag", display version ]+ out <- rawSystemStdout verbosity "darcs" ["changes", "--context"]+ return $ Just out+ `Exception.catch` \_ -> return Nothing++ contextDist <- parseFile contextFile+ return $ case (contextDarcs, contextDist) of+ (Just x, _) -> Just x+ (Nothing, Just x) -> Just x+ (Nothing, Nothing) -> Nothing+ where contextFile = "release/distributed-context"++parseFile :: (Read a) => String -> IO (Maybe a)+parseFile f = do+ exist <- doesFileExist f+ if exist then do+ content <- readFile f -- ^ ratify readFile: we don't care here.+ case reads content of+ ((s,_):_) -> return s+ _ -> return Nothing+ else return Nothing++-------------------------------------+-- Running the testsuite+--++data TestKind = Bug | Test | Network deriving Eq++instance Show TestKind where+ show Bug = "bugs"+ show Test = "tests"+ show Network = "tests/network"++flat :: (Show a) => a -> String+flat a = [ if x == '/' then '_' else x | x <- show a ]++isTest :: FilePath -> Bool+isTest = (".sh" `isSuffixOf`)++execTests :: FilePath -> TestKind -> String -> [String] -> IO ()+execTests darcs_path k fmt tests = do+ let dir = (flat k) ++ "-" ++ fmt ++ ".dir"+ rmRf dir+ cloneTree (show k) dir+ withCurrentDirectory dir $ do+ createDirectory ".darcs"+ when (not $ null fmt) $ appendFile ".darcs/defaults" $ "ALL " ++ fmt ++ "\n"+ putStrLn $ "Running tests for format: " ++ fmt+ exec+ where exec = do+ fs <- case tests of+ [] -> sort `fmap` getDirectoryContents "."+ x -> return x+ cwd <- getCurrentDirectory+ let run = filter isTest fs+ res <- Harness.runTests (Just darcs_path) cwd run+ when ((not res) && (k /= Bug)) $ fail "Tests failed"+ return ()++individualTests :: FilePath -> [String] -> IO ()+individualTests darcs_path tests = do+ run <- concat `fmap` mapM find tests+ sequence_ [ do exec kind [test | (kind', test) <- run, kind' == kind]+ | kind <- [Test, Bug, Network] ]+ where tryin w t' = [w </> t', w </> (t' ++ ".sh")]+ exec _ [] = return ()+ exec kind to_run = allTests darcs_path kind to_run+ find t = do+ let c = [t, t ++ ".sh"] ++ tryin "tests" t ++ tryin "bugs" t+ ++ tryin "network" t+ run <- map kindify `fmap` filterM doesFileExist c+ return $ take 1 run+ kindify test = case splitDirectories test of+ [p, y] -> (parse_kind p, y)+ _ -> error $ "Bad format in " ++ test +++ ": expected type/test"+ parse_kind "tests" = Test+ parse_kind "bugs" = Bug+ parse_kind "network" = Network+ parse_kind x = error $ "Test prefix must be one of " +++ "[tests, bugs, network] in " ++ x+++allTests :: FilePath -> TestKind -> [String] -> IO ()+allTests darcs_path k s =+ do test `mapM` repotypes+ return ()+ where repotypes = ["darcs-2", "hashed", "old-fashioned-inventory"]+ test x = execTests darcs_path k x s++-------------------------------------------------------+-- Utility functions (FIXME)+-- copy & paste & edit: darcs wants to share these+--++withCurrentDirectory :: FilePath -> IO a -> IO a+withCurrentDirectory name m =+ bracket+ (do cwd <- getCurrentDirectory+ when (name /= "") (setCurrentDirectory name)+ return cwd)+ (\oldwd -> setCurrentDirectory oldwd `catch` (\_ -> return ()))+ (const m)++cloneTree :: FilePath -> FilePath -> IO ()+cloneTree = cloneTreeExcept []++cloneTreeExcept :: [FilePath] -> FilePath -> FilePath -> IO ()+cloneTreeExcept except source dest =+ do isdir <- doesDirectoryExist source+ if isdir then do+ createDirectoryIfMissing True dest+ fps <- getDirectoryContents source+ let fps' = filter (`notElem` (".":"..":except)) fps+ mk_source fp = source ++ "/" ++ fp+ mk_dest fp = dest ++ "/" ++ fp+ zipWithM_ cloneSubTree (map mk_source fps') (map mk_dest fps')+ else fail ("cloneTreeExcept: Bad source " ++ source)+ `catch` fail ("cloneTreeExcept: Bad source " ++ source)++cloneSubTree :: FilePath -> FilePath -> IO ()+cloneSubTree source dest =+ do isdir <- doesDirectoryExist source+ isfile <- doesFileExist source+ if isdir then do+ createDirectory dest+ fps <- getDirectoryContents source+ let fps' = filter (`notElem` [".", ".."]) fps+ mk_source fp = source ++ "/" ++ fp+ mk_dest fp = dest ++ "/" ++ fp+ zipWithM_ cloneSubTree (map mk_source fps') (map mk_dest fps')+ else if isfile then do+ cloneFile source dest+ else fail ("cloneSubTree: Bad source "++ source)+ `catch` (\e -> if isDoesNotExistError e+ then return ()+ else ioError e)++cloneFile :: FilePath -> FilePath -> IO ()+cloneFile = copyFile++rmRf :: FilePath -> IO ()+rmRf path = do+ isdir <- doesDirectoryExist path+ isf <- doesFileExist path+ when isdir $ removeDirectoryRecursive path+ when isf $ removeFile path+ return ()++-- (END FIXME)++\end{code}
@@ -0,0 +1,36 @@+#!/usr/bin/env bash+## Darcs should refuse to add an unreadable file, because unreadable+## files aren't recordable.+##+## Copyright (C) 2005 Mark Stosberg+##+## Permission is hereby granted, free of charge, to any person+## obtaining a copy of this software and associated documentation+## files (the "Software"), to deal in the Software without+## restriction, including without limitation the rights to use, copy,+## modify, merge, publish, distribute, sublicense, and/or sell copies+## of the Software, and to permit persons to whom the Software is+## furnished to do so, subject to the following conditions:+##+## The above copyright notice and this permission notice shall be+## included in all copies or substantial portions of the Software.+##+## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+## EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+## MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND+## NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS+## BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN+## ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN+## CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+## SOFTWARE.++. ../tests/lib++abort_windows # does not work on Windows.+rm -rf temp1 # Another script may have left a mess.+darcs initialize --repodir temp1+touch temp1/unreadable+chmod a-r temp1/unreadable # Make the file unreadable.+not darcs add --repodir temp1 no_perms.txt 2>temp1/log+fgrep -i 'permission denied' temp1/log+rm -rf temp1
@@ -0,0 +1,18 @@+#!/usr/bin/env bash+set -ev++# tests for "darcs dist"++not () { "$@" && exit 1 || :; }++rm -rf temp1+mkdir temp1+cd temp1+darcs init+# needs fixed on FreeBSD+darcs dist -v 2> log+not grep error log+cd ..++rm -rf temp1+
@@ -0,0 +1,46 @@+#!/usr/bin/env bash++# This appears to hang on Windows++set -ev+# TODO: is this really enough to make all commands interpret the given strings +# as latin1?+export LANG="en_US.ISO-8859-1"++rm -rf temp1+rm -rf temp2+mkdir temp1+mkdir temp2+cd temp1++seventysevenaddy="<aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa@bbbbbbbbbb.cccccccccc.abrasoft.com>"++darcs init++echo "Have you seen the smørrebrød of René Çavésant?" > non_ascii_file+darcs add non_ascii_file+darcs record -am "non-ascii file add" -A test++cd ../temp2+darcs init+cd ../temp1++# long email adress: check that email adresses of <= 77 chars don't get split up+darcs send --from="Kjålt Überström $seventysevenaddy" \+ --subject "Un patch pour le répositoire" \+ --to="Un garçon français <garcon@francais.fr>" \+ --sendmail-command='cp /dev/stdin mail_as_file %<' \+ -a ../temp2++cat mail_as_file+# The long mail address should be in there as a whole+grep $seventysevenaddy mail_as_file++# Check that there are no non-ASCII characters in the mail+ghc -e 'getContents >>= return . not . any (> Data.Char.chr 127)' < mail_as_file | grep '^True$'+++cd ..+rm -rf temp1+rm -rf temp2+
@@ -0,0 +1,53 @@+#!/usr/bin/env bash+set -ev+export DARCS_EMAIL=test+rm -rf tmp_d1 tmp_d2 tmp_d+# Preparations:+# Set up two repos, each with a patch (d1 and d2 respectively) with both+# an individual change and a change that is identical in both repos,+# and thus auto-merge, i.e., they don't conflict in darcs2. Pull them+# together and record a patch (problem) on top of the auto-merged change,+# so that it depends on EITHER of two patches.++mkdir tmp_d1; cd tmp_d1+darcs init --darcs-2+echo a > a+echo b > b+echo c > c+darcs rec -alm init+echo a-independent > a+echo c-common > c+darcs rec -am d1+cd ..+darcs get --to-patch init tmp_d1 tmp_d2+cd tmp_d2+echo b-independent > b+echo c-common > c+darcs rec -am d2+darcs pull -a ../tmp_d1+# no conflicts -- c-common is identical+echo c-problem > c+darcs rec -am problem+cd ..++# I want to pull the 'problem' patch, but expect darcs to get confused+# because it doesn't know how to select one of the two dependent patches.++darcs get --to-patch init tmp_d2 tmp_d+cd tmp_d+echo n/n/y |tr / \\012 |darcs pull ../tmp_d2+darcs cha++# This is weird, we got d2 though we said No. I would have expected+# darcs to skip the 'problem' patch in this case.++# Try to pull d1 and unpull d2.++darcs pull -a ../tmp_d1+exit 1 # darcs hangs here (2.0.2+77)!+echo n/y/d |tr / \\012 |darcs obl -p d2++# The obliterate fails with: patches to commute_to_end does not commutex (1)++cd ..+rm -rf tmp_d1 tmp_d2 tmp_d
@@ -0,0 +1,60 @@+#!/usr/bin/env bash+set -ev++# Set up a base repo. Our experiment will start from this point+mkdir base+cd base+darcs init --darcs-2+printf "Line1\nLine2\nLine3\n" > foo+darcs rec -alm Base+cd ..++# Now we want to record patch A, which will turn "Line2" into "Hello"+darcs get base a+cd a+printf "Line1\nHello\nLine3\n" > foo+darcs rec --ignore-times -am A+cd ..++# Make B the same as A+darcs get base b+cd b+printf "Line1\nHello\nLine3\n" > foo+darcs rec --ignore-times -am B+cd ..++# Now we make a patch C that depends on A+darcs get a ac+cd ac+printf "Line1\nWorld\nLine3\n" > foo+darcs rec --ignore-times -am C+cd ..++# Merge A and B+darcs get a ab+cd ab+darcs pull -a ../b+darcs revert -a+cd ..++# And merge in C too+darcs get ab abc+cd abc+darcs pull -a ../ac+darcs revert -a+cd ..++# Now we can pull just B and C into base+darcs get base bc+cd bc+darcs pull ../abc -ap 'B|C'+cd ..++# Now we have base, B and C in a repository. At this point we're correct.++# Let's try merging AC with BC now, here we discover a bug.++darcs get ac abc2+cd abc2+darcs pull -a ../bc+
@@ -0,0 +1,43 @@+#!/usr/bin/env bash+## Test for issue1190 - conflicts between HUNK and REPLACE are not+## marked by --mark-conflicts.+##+## Copyright (C) 2009 Trent W. Buck+##+## Permission is hereby granted, free of charge, to any person+## obtaining a copy of this software and associated documentation+## files (the "Software"), to deal in the Software without+## restriction, including without limitation the rights to use, copy,+## modify, merge, publish, distribute, sublicense, and/or sell copies+## of the Software, and to permit persons to whom the Software is+## furnished to do so, subject to the following conditions:+##+## The above copyright notice and this permission notice shall be+## included in all copies or substantial portions of the Software.+##+## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+## EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+## MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND+## NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS+## BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN+## ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN+## CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+## SOFTWARE.++. ../tests/lib++rm -rf d e # Another script may have left a mess.+darcs init --repo d/+printf %s\\n foo bar baz >d/f+darcs record --repo d/ -lam f1+darcs get d/ e/+darcs replace --repo e/ --force bar baz f+darcs record --repo e/ -lam replacement+printf %s\\n foo bar quux >d/f # replace baz with quux+darcs record --repo d/ -am f2++## There ought to be a conflict here, and there is.+darcs pull --repo e/ d/ -a --mark-conflicts+## The file ought to now have conflict markers in it.+grep 'v v v' e/f+rm -rf d/ e/ # Clean up after ourselves.
@@ -0,0 +1,21 @@+#!/usr/bin/env bash+set -ev++not () { "$@" && exit 1 || :; }++rm -rf temp1+mkdir temp1+cd temp1++darcs init+touch aargh+darcs add aargh+echo utrecht > aargh++darcs wh foo foo/../foo/. > out+cat out+not grep utrecht out++cd ..+rm -rf temp1+
@@ -0,0 +1,33 @@+#!/usr/bin/env bash+## Test for issue1266 - attempting to initialize a repository inside+## another repository should cause a warning, because while perfectly+## legitimate, it is likely to be accidental.+##+## Copyright (C) 2009 Trent W. Buck+##+## Permission is hereby granted, free of charge, to any person+## obtaining a copy of this software and associated documentation+## files (the "Software"), to deal in the Software without+## restriction, including without limitation the rights to use, copy,+## modify, merge, publish, distribute, sublicense, and/or sell copies+## of the Software, and to permit persons to whom the Software is+## furnished to do so, subject to the following conditions:+##+## The above copyright notice and this permission notice shall be+## included in all copies or substantial portions of the Software.+##+## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+## EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+## MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND+## NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS+## BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN+## ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN+## CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+## SOFTWARE.++. ../tests/lib++rm -rf temp1 out # Another script may have left a mess.+darcs init --repodir temp1+darcs init --repodir temp1/temp2 2>&1 | tee out+grep -i WARNING out # A warning should be printed.
@@ -0,0 +1,27 @@+#!/usr/bin/env bash+set -ev++# See issue1327.+# results in the error:+# patches to commute_to_end does not commutex (1) at src/Darcs/Patch/Depends.hs:452+++rm -rf temp1 temp2+mkdir temp1+cd temp1+darcs init+echo fileA version 1 > fileA+echo fileB version 1 > fileB+darcs add fileA fileB+darcs record --author foo@bar --ignore-times --all -m "Add fileA and fileB"+echo fileA version 2 > fileA+darcs record --author foo@bar --ignore-times --all -m "Modify fileA"+cd ..+darcs get temp1 temp2+cd temp2+darcs obliterate -p "Modify fileA" --all+darcs unrecord -p "Add fileA and fileB" --all+darcs record --author foo@bar --ignore-times --all fileA -m "Add just fileA"+cd ../temp1+darcs pull --all ../temp2+echo y | darcs obliterate --dont-prompt-for-dependencies -p "Add fileA and fileB"
@@ -0,0 +1,37 @@+#!/usr/bin/env bash+## Test for issue1337 - darcs changes shows unrelated patches+## Asking "darcs changes" about an unrecorded file d/f will list the+## patch that creates the parent directory d/ (instead of no patches).+##+## Copyright (C) 2009 Trent W. Buck+##+## Permission is hereby granted, free of charge, to any person+## obtaining a copy of this software and associated documentation+## files (the "Software"), to deal in the Software without+## restriction, including without limitation the rights to use, copy,+## modify, merge, publish, distribute, sublicense, and/or sell copies+## of the Software, and to permit persons to whom the Software is+## furnished to do so, subject to the following conditions:+##+## The above copyright notice and this permission notice shall be+## included in all copies or substantial portions of the Software.+##+## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+## EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+## MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND+## NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS+## BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN+## ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN+## CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+## SOFTWARE.++. ../tests/lib++rm -rf temp1+darcs init --repodir temp1+cd temp1+mkdir d+darcs record -lam d d+# We use --match 'touch d/f' instead of simply d/f because the latter+# prints "Changes to d/f:\n" before the count.+test 0 -eq "$(darcs changes --count --match 'touch d/f')"
@@ -0,0 +1,32 @@+#!/usr/bin/env bash+set -ev++. ../tests/lib++rm -rf temp1 temp1a temp1b++mkdir temp1+cd temp1+darcs init+darcs setpref test 'echo nothing'+darcs record -am 'null pref'+cd ..++darcs get temp1 temp1a+cd temp1a+darcs setpref test 'echo a'+darcs record -am 'pref a'+cd ..++darcs get temp1 temp1b+cd temp1b+darcs setpref test 'echo b'+darcs record -am 'pref b'+cd ..++cd temp1+darcs pull -a ../temp1a --dont-allow-conflicts+not darcs pull -a ../temp1b --dont-allow-conflicts+cd ..++rm -rf temp1 temp1a temp1b
@@ -0,0 +1,44 @@+#!/usr/bin/env bash+## Test for issue1401 - when two repos share a HUNK patch, but that+## patch's ADDFILE dependency is met by different patches in each+## repo, it becomes impossible to pull between the two repos.+##+## Copyright (C) 2009 Trent W. Buck+##+## Permission is hereby granted, free of charge, to any person+## obtaining a copy of this software and associated documentation+## files (the "Software"), to deal in the Software without+## restriction, including without limitation the rights to use, copy,+## modify, merge, publish, distribute, sublicense, and/or sell copies+## of the Software, and to permit persons to whom the Software is+## furnished to do so, subject to the following conditions:+##+## The above copyright notice and this permission notice shall be+## included in all copies or substantial portions of the Software.+##+## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+## EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+## MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND+## NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS+## BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN+## ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN+## CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+## SOFTWARE.++. ../tests/lib++## This bug only affects darcs-2 repositories.+fgrep darcs-2 ~/.darcs/defaults &>/dev/null || exit 0++rm -rf d e # Another script may have left a mess.+darcs initialize --repodir d/+darcs initialize --repodir e/+touch d/f d/g e/f+darcs record --repodir d/ -lam 'Add f and g'+darcs record --repodir e/ -lam 'Add f'+echo >d/f+darcs record --repodir d/ -am 'Change f'+darcs pull --repodir e/ -a d/+darcs obliterate --repodir e/ -ap 'Add f and g'+darcs pull --repodir e/ -a d/+rm -rf d/ e/ # Clean up after ourselves.
@@ -0,0 +1,19 @@+#!/bin/sh+rm -rf temp1+darcs init --repodir temp1++cd temp1++echo "test exit 1" > _darcs/prefs/prefs+echo "name" > _darcs/prefs/author+echo "a" > a+darcs record --look-for-adds --no-test --all --patch-name=p1+echo "b" >> a+echo "y" | darcs amend-record --all --patch=p1++# There should be one patch in the repo+test 1 -eq `darcs changes --count` || exit 1++# Another check: there should be nothing new after a is restored+echo "a" > a+darcs whatsnew -l && exit 1 || exit 0
@@ -0,0 +1,50 @@+#!/usr/bin/env bash+## -*- coding: utf-8 -*-+## Test for issue1442 - if we disable Darcs escaping and don't change+## our encoding, the bytes in a filename should be the same bytes that+## "darcs changes -v" prints to the right of "addfile" and "hunk".+##+## Copyright (C) 2009 Trent W. Buck+##+## Permission is hereby granted, free of charge, to any person+## obtaining a copy of this software and associated documentation+## files (the "Software"), to deal in the Software without+## restriction, including without limitation the rights to use, copy,+## modify, merge, publish, distribute, sublicense, and/or sell copies+## of the Software, and to permit persons to whom the Software is+## furnished to do so, subject to the following conditions:+##+## The above copyright notice and this permission notice shall be+## included in all copies or substantial portions of the Software.+##+## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+## EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+## MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND+## NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS+## BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN+## ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN+## CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+## SOFTWARE.++. ../tests/lib++rm -rf R # Another script may have left a mess.++## Use the first UTF-8 locale we can find.+export LC_ALL=$(locale -a | egrep 'utf8|UTF-8' | head -1)+export DARCS_DONT_ESCAPE_ANYTHING=True++## If LC_ALL is the empty string, this system doesn't support UTF-8.+if test -z "$LC_ALL"+then exit 1+fi++darcs init --repo R+echo '首頁 = א₀' >R/'首頁 = א₀'+darcs record --repo R -lam '首頁 = א₀' '首頁 = א₀'+darcs changes --repo R -v '首頁 = א₀' >R/log+#cat R/log # Show the humans what the output was.+grep -c '首頁 = א₀' R/log >R/count+echo 5 >R/expected-count+cmp R/count R/expected-count # Both count files should contain "5\n".+rm -rf R # Clean up after ourselves.
@@ -0,0 +1,83 @@+#!/usr/bin/env bash+## Test for issue1461 - patches to files whose names only differ by+## case can be wrongly applied to the same file in the working directory.+##+## Copyright (C) 2009 Eric Kow+##+## Permission is hereby granted, free of charge, to any person+## obtaining a copy of this software and associated documentation+## files (the "Software"), to deal in the Software without+## restriction, including without limitation the rights to use, copy,+## modify, merge, publish, distribute, sublicense, and/or sell copies+## of the Software, and to permit persons to whom the Software is+## furnished to do so, subject to the following conditions:+##+## The above copyright notice and this permission notice shall be+## included in all copies or substantial portions of the Software.+##+## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+## EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+## MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND+## NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS+## BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN+## ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN+## CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+## SOFTWARE.++. ../tests/lib # Load some portability helpers.+rm -rf lower upper joint # Another script may have left a mess.+mkdir lower upper++cd lower+darcs init+cat > a << EOF+1+2+3+EOF+darcs add a+darcs record -am 'lower init a'+cd ..++cd upper+darcs init+cat > A << EOF+1+2+3+EOF+darcs add A+darcs record -am 'upper init A'+cd ..++darcs get lower joint+cd joint+darcs pull -a ../upper+cd ..++cd lower+cat > a << EOF+one lower+2+3+EOF+darcs record -am 'lower modify'+cd ..++cd upper+cat > A << EOF+1+2+three upper+EOF+darcs record -am 'upper modify'+cd ..++cd joint+darcs pull ../lower -a+darcs pull ../upper -a+grep one a && not grep three a+grep three A && not grep one A+cd ..+# clean up after ourselves+rm -rf lower upper joint
@@ -0,0 +1,21 @@+#!/usr/bin/env bash+set -ev++. ../tests/lib+rm -rf temp+mkdir temp+cd temp+darcs init+mkdir a b+touch a/a b/b+darcs add --rec .+darcs record -a -m ab -A test+darcs annotate a/a+# annotate --repodir=something '.' should work+cd ..+darcs annotate --repodir temp '.'+cd temp++cd ..+rm -rf temp+
@@ -0,0 +1,43 @@+#!/bin/sh++set -ev++rm -rf temp1 temp2+mkdir temp1 temp2+cd temp1+darcs init+echo first > a+darcs add a+darcs record --pipe --all --patch-name=first <<EOF+Thu Sep 18 22:37:06 MSD 2008+author+EOF++echo first > b+darcs add b+darcs record --pipe --all --patch-name=first <<EOF+Thu Sep 18 22:37:06 MSD 2008+author+EOF++cd ../temp2+darcs init+darcs pull --all --dont-allow-conflicts ../temp1+echo second >> a+darcs record --pipe --all --patch-name=second <<EOF+Thu Sep 18 22:37:07 MSD 2008+author+EOF++cd ../temp1+echo second >> b+darcs record --pipe --all --patch-name=second <<EOF+Thu Sep 18 22:37:07 MSD 2008+author+EOF++darcs pull --all --dont-allow-conflicts ../temp2+test `darcs changes --count` = "4"+cd ..++rm -rf temp1 temp2
@@ -0,0 +1,33 @@+#!/usr/bin/env bash++# For issue390: darcs whatsnew somefile" lstats every file in the working copy and pristine/ directory++set -ev++if ! test -x "$(which strace)"+then echo skipping test since strace was not found+ exit+fi++rm -rf temp+mkdir temp+cd temp+darcs init+date > file1+date > file2+darcs add file*+darcs record -am "test"++strace darcs whatsnew file1 &> out+# we should be accessing file1+grep file1 out+# but shouldn't be accessing file2+if grep file2 out+then+ echo A whatsnew for file1 should not involve a 'stat' call to file2+ exit 1+else+ echo Yay. We pass.+fi++rm -rf temp
@@ -0,0 +1,29 @@+#!/usr/bin/env bash++# For issue588, "amend-record --look-for-adds end up with two "addfile" entries"++set -ev+rm -rf temp1+darcs init --repodir temp1++export EMAIL=me++cd temp1++date > f+darcs add f+darcs rec -am p1+for (( i=0 ; i < 500; i=i+1 )); do+ echo $i >> f;+ darcs rec -am p$i+done++darcs changes 2> err | head++touch correcterr++diff correcterr err++cd ..++rm -rf temp1
@@ -0,0 +1,46 @@+#!/usr/bin/env bash++set -ev++test $DARCS || DARCS=$PWD/../darcs+ACTUAL_DARCS=`which $DARCS`+DARCSPATH=`dirname $ACTUAL_DARCS`+PATH="$DARCSPATH:$PATH"+export PATH++# create base repository+rm -rf temp1+mkdir temp1+cd temp1+darcs init++echo first > a+darcs add a+darcs record -am first+darcs tag --checkpoint 'Tag 1'++echo second > b+darcs add b+darcs record -am second+darcs tag --checkpoint 'Tag 2'++echo third > c+darcs add c+darcs record -am third+darcs tag --checkpoint 'Tag 3'++# create a partial copy of the base repository and modify it+cd ..+rm -rf temp2+darcs get --partial temp1 temp2+cd temp2++# instead of the following three commands one could also use darcs optimize+echo mistake > a+darcs record -am mistake+darcs unrecord -ap mistake++# now check the repository+darcs check+# => darcs: failed to read patch: ...+
@@ -0,0 +1,47 @@+#!/usr/bin/env bash+set -ev++# Note that this is fixed, the lines marked # BUG HERE+# should be moved back into merging_newlines.sh++# trick: requiring something to fail+not () { "$@" && exit 1 || :; }++rm -rf temp1 temp2++# set up the repository+mkdir temp1+cd temp1+darcs init+cd ..++cd temp1+echo "apply allow-conflicts" > _darcs/prefs/defaults+# note: to make this pass, change echo to echo -n+# is that right?+echo "from temp1" > one.txt+darcs add one.txt+darcs record -A bar -am "add one.txt"+echo >> one.txt+darcs wh -u+cd ..++darcs get temp1 temp2+cd temp2+# reality check+darcs show files | grep one.txt+echo "in tmp2" >> one.txt+darcs whatsnew -s | grep M+darcs record -A bar -am "add extra line"+darcs annotate -p . -u+darcs push -av > log+cat log+not grep -i conflicts log+# BUG HERE+# after a conflict, darcs resolve should report a conflict+darcs mark-conflicts > log 2>&1+cat log+not grep -i 'no conflicts' log+cd ..++rm -rf temp1 temp2
@@ -0,0 +1,28 @@+#!/usr/bin/env bash+set -ev++rm -rf temp1 temp2++# set up the repository+mkdir temp1+cd temp1+darcs init+cd ..++cd temp1+echo -n "from temp1" > one.txt+darcs add one.txt+darcs record -A bar -am "add one.txt"+echo >> one.txt+cd ..++darcs get temp1 temp2+cd temp2+echo "in tmp2" >> one.txt+darcs record -A bar -am "add extra line"+lines_added=`darcs changes -v --last=1 | grep '\+' | wc -l`+echo $lines_added+test $lines_added -eq 1+cd ..++rm -rf temp1 temp2
@@ -0,0 +1,35 @@+#!/bin/sh++set -ev++rm -rf temp1 temp2+mkdir temp1+cd temp1+darcs init+echo first > a+darcs add a+darcs record --pipe --all --patch-name=first <<EOF+Thu Sep 18 22:37:06 MSD 2008+author+EOF++echo first > b+darcs add b+darcs record --pipe --all --patch-name=first <<EOF+Thu Sep 18 22:37:06 MSD 2008+author+EOF++# it seems that somehow the following occasionally fails on an nfs+# filesystem when darcs is compiled with ghc 6.6.++darcs get . ../temp2++# it fails with something like:++# darcs: ./_darcs/patches/20080918223706-f64cd-6b512400a8108808b7ba7057f0eac2adf473b3ae.gz: copyFile: resource busy (file is locked)+++cd ..++rm -rf temp1 temp2
@@ -0,0 +1,36 @@+#!/usr/bin/env bash+## Test for issueN - darcs record shouldn't access old inventories!+##+## Copyright (C) 2008 David Roundy+##+## Permission is hereby granted, free of charge, to any person+## obtaining a copy of this software and associated documentation+## files (the "Software"), to deal in the Software without+## restriction, including without limitation the rights to use, copy,+## modify, merge, publish, distribute, sublicense, and/or sell copies+## of the Software, and to permit persons to whom the Software is+## furnished to do so, subject to the following conditions:+##+## The above copyright notice and this permission notice shall be+## included in all copies or substantial portions of the Software.+##+## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+## EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+## MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND+## NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS+## BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN+## ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN+## CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+## SOFTWARE.++. ../tests/lib # Load some portability helpers.+which strace # This test requires strace(1).+rm -rf R # Another script may have left a mess.+darcs init --repo R # Create our test repo.+touch R/a-unique-filename+strace -eopen -oR/trace \+ darcs record --repo R -lam 'A unique commit message.'+grep a-unique-filename R/trace+grep _darcs/hashed_inventory R/trace+not grep _darcs/inventories/ R/trace+rm -rf R # Clean up after ourselves.
@@ -0,0 +1,52 @@+#!/usr/bin/env bash++# test for broken time stamps++set -ev++. lib.sh++rm -rf temp+mkdir temp+cd temp+darcs init --hashed++echo this is my favorite test > foobar+darcs add foobar+sleep 2 # so the time stamps won't accidentally be identical+darcs record -am 'add foobar'++HASHVAL=0000000025-4cbbfd8ce543076b132b13b60ae06d0189ee80b4d5908abb5e060d331d25eb5c++if test -d _darcs/pristine.hashed;+then+ ls _darcs/pristine.hashed+ test -f _darcs/pristine.hashed/$HASHVAL+ # verify that the modification time of the file in the pristine cache+ # is identical to the modification time of the file in the working+ # directory.+ not test foobar -ot _darcs/pristine.hashed/$HASHVAL+ not test foobar -nt _darcs/pristine.hashed/$HASHVAL+else+ not test foobar -ot _darcs/pristine/foobar+ not test foobar -nt _darcs/pristine/foobar+fi++# Now let's verify that the test actually works...++sleep 1+touch foobar++if test -d _darcs/pristine.hashed;+then+ not test foobar -ot _darcs/pristine.hashed/$HASHVAL+ test foobar -nt _darcs/pristine.hashed/$HASHVAL+else+ not test foobar -ot _darcs/pristine/foobar+ test foobar -nt _darcs/pristine/foobar+fi+++cd ..++rm -rf temp
@@ -0,0 +1,36 @@+#compdef darcs+## Darcs completion snippet for zsh.+##+## Copyright (C) 2009 Nicolas Pouillard+##+## This program is free software; you can redistribute it and/or modify+## it under the terms of the GNU General Public License as published by+## the Free Software Foundation; either version 2 of the License, or+## (at your option) any later version.+##+## This program is distributed in the hope that it will be useful,+## but WITHOUT ANY WARRANTY; without even the implied warranty of+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+## GNU General Public License for more details.++if (($CURRENT == 2)); then+ # We're completing the first word after "darcs" -- the command.+ _wanted command expl 'darcs command' \+ compadd -- $( darcs --commands )+else+ case "${words[$CURRENT]}"; in+ # If it looks like an URL...+ ht*|ft*)+ _arguments '*:URL:_urls'+ ;;+ # If it looks like an explicit path...+ /*|./*|\~*|../*)+ _arguments '*:file:_files'+ ;;+ # Otherwise, let's ask darcs for all possible options+ *)+ _wanted args expl 'arg for darcs command' \+ compadd -- $( darcs ${words[2]} --list-option )+ ;;+ esac+fi
@@ -0,0 +1,42 @@+darcs.cgi is the darcs repository viewer. It provides a web interface+for viewing darcs repositories, using XSLT to transform darcs' XML+output into XHTML. It is written in perl and is more featureful than+the older haskell cgi program darcs_cgi.lhs (seen in URLs as "darcs").++dependencies++ darcs.cgi requires with the following tools and has been tested with+ the version mentioned:++ darcs-1.0.1 http://www.darcs.net+ libxslt-1.1.6 http://xmlsoft.org/XSLT/+ perl-5.8.0 http://www.perl.com++installation++ 1) copy darcs.cgi to your webserver's cgi directory, eg+ /usr/lib/cgi-bin. A symlink probably won't do. Make+ sure it's executable.+ 2) copy cgi.conf to @sysconfdir@/darcs/cgi.conf and edit appropriately.+ You can overwrite the cgi.conf used by the old darcs cgi script.+ You probably don't need to change anything here.+ 3) copy the xslt directory to @datadir@/darcs/xslt+ 4) copy xslt/styles.css to @sysconfdir@/darcs/styles.css++ Test by browsing "http://<host>/cgi-bin/darcs.cgi/".++troubleshooting++ if the configuration is incorrect error messages will be printed to+ the webserver's error log, for example "/var/log/apache/error.log".++ you can also double check the configuration by running darcs.cgi+ from the command line and supplying a --check argument:++ $ ./darcs.cgi --check++character encodings++ if your repositories contain files with characters encoded in+ something other than ASCII or UTF-8, change the 'xml_encoding'+ setting in cgi.conf to the appropriate encoding.
@@ -0,0 +1,31 @@+# This is an example for cgi.conf++# reposdir is the directory containing the repositories++reposdir = /var/www/repos++# cachedir is a directory writable by www-data (or whatever user your cgi+# scripts run as) which is used to cache the web pages.++cachedir = /var/cache/darcs++# The following are used by darcs.cgi (not darcs_cgi)+PATH = /bin:/usr/bin:/usr/local/bin++# paths to executables, or just the executables if they are in 'PATH'+#darcs = darcs+#xsltproc = xsltproc++# Path to XSLT templates (default is /usr/local/share/darcs/xslt)+xslt_dir = @datadir@/darcs/xslt++# HTTP request path of the style sheet, the default will magically read +# @sysconfdir@/darcs/styles.css+#stylesheet = /cgi-bin/darcs.cgi/styles.css++css_styles = @sysconfdir@/darcs/styles.css++# encoding to include in XML declaration. Set this to the encoding used+# by the files in your repositories.++xml_encoding = UTF-8
@@ -0,0 +1,491 @@+#!/usr/bin/perl -T+#+# darcs.cgi - the darcs repository viewer+#+# Copyright (c) 2004 Will Glozer+#+# Permission is hereby granted, free of charge, to any person obtaining+# a copy of this software and associated documentation files (the+# "Software"), to deal in the Software without restriction, including+# without limitation the rights to use, copy, modify, merge, publish,+# distribute, sublicense, and/or sell copies of the Software, and to+# permit persons to whom the Software is furnished to do so, subject to+# the following conditions+#+# The above copyright notice and this permission notice shall be+# included in all copies or substantial portions of the Software.+#+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.++#+# This program calls darcs (or its own subroutines) to generate XML+# which is rendered into HTML by XSLT. It is capable of displaying+# the files in a repository, various patch histories, annotations, etc.+#++use strict;++use CGI qw( :standard );+use CGI::Util;+use File::Basename;+use File::stat;+use IO::File;+use POSIX;++## the following variables can be customized to reflect your system+## configuration by defining them appropriately in the file+## "@sysconfdir@/darcs/cgi.conf". The syntax accepts equals signs or simply+## blanks separating values from assignments.++$ENV{'PATH'} = read_conf('PATH', $ENV{'PATH'});++# path to executables, or just the executable if they are in $ENV{'PATH'}+my $darcs_program = read_conf("darcs", "darcs");+my $xslt_program = read_conf("xsltproc", "xsltproc");++# directory containing repositories+my $repository_root = read_conf("reposdir", "/var/www");++# XSLT template locations+my $template_root = read_conf("xslt_dir", '@datadir@/darcs/xslt');++my $xslt_annotate = "$template_root/annotate.xslt";+my $xslt_browse = "$template_root/browse.xslt";+my $xslt_patches = "$template_root/patches.xslt";+my $xslt_repos = "$template_root/repos.xslt";+my $xslt_rss = "$template_root/rss.xslt";++my $xslt_errors = "$template_root/errors.xslt";++# CSS stylesheet that XSLT templates refer to. This is a HTTP request+# path, not a local file system path. The default will cause darcs.cgi+# to serve the stylesheet rather than the web server.+my $stylesheet = read_conf("stylesheet", "/cgi-bin/darcs.cgi/styles.css");++# location of the CSS stylesheet that darcs.cgi will serve if it+# receives a request for '/styles.css'+my $css_styles = read_conf("css_styles", '@sysconfdir@/darcs/styles.css');++# location of the favicon that darcs.cgi will serve if it+# receives a request for '/[\w\-]+/favicon.ico'+my $favicon = read_conf("favicon", "/cgi-bin/favicon.ico");++# XML source for the error pages+my $xml_errors = "$template_root/errors.xml";++# encoding to include in XML declaration+my $xml_encoding = read_conf("xml_encoding", "UTF-8");++## end customization++# ----------------------------------------------------------------------++# read a value from the cgi.conf file.+{+ my(%conf);++ sub read_conf {+ my ($flag, $val) = @_;+ $val = "" if !defined($val);+ + if (!%conf && open(CGI_CONF, '@sysconfdir@/darcs/cgi.conf')) {+ while (<CGI_CONF>) {+ chomp;+ next if /^\s*(?:\#.*)?$/; # Skip blank lines and comment lines+ if (/^\s*(\S+)\s*(?:\=\s*)?(\S+)\s*$/) {+ $conf{$1} = $2;+ # print "read_conf: $1 = $2\n";+ } else {+ warn "read_conf: $_\n";+ }+ }+ close(CGI_CONF);+ }++ $val = $conf{$flag} if exists($conf{$flag});++ return $val;+ }+}++# open xsltproc to transform and output `xml' with stylesheet file `xslt'+sub transform {+ my ($xslt, $args, $content_type) = @_;++ $| = 1;+ printf "Content-type: %s\r\n\r\n", $content_type || "text/html";+ my $pipe = new IO::File "| $xslt_program $args $xslt -";+ $pipe->autoflush(0);+ return $pipe;+}++sub pristine_dir {+ my ($repo) = @_;+ my $pristine = "current";+ if (! -d "${repository_root}/${repo}/_darcs/$pristine") {+ $pristine = "pristine";+ }+ return "${repository_root}/${repo}/_darcs/$pristine";+}++# begin an XML document with a root element and the repository path+sub make_xml {+ my ($fh, $repo, $dir, $file) = @_;+ my ($full_path, $path) = '/';++ printf $fh qq(<?xml version="1.0" encoding="$xml_encoding"?>\n);++ printf $fh qq(<darcs repository="$repo" target="%s/%s%s">\n),+ $repo, ($dir ? "$dir/" : ''), ($file ? "$file" : '');++ print $fh qq(<path>\n);+ foreach $path (split('/', "$repo/$dir")) {+ $full_path .= "$path/";+ print $fh qq(<directory full-path="$full_path">$path</directory>\n);+ }+ if ($file) {+ print $fh qq(<file full-path="$full_path$file">$file</file>\n) if $file;+ }+ print $fh qq(</path>\n\n);+}++# finish XML output+sub finish_xml {+ my ($fh) = @_;+ print $fh "\n</darcs>\n";+ $fh->flush;+}++# run darcs and wrap the output in an XML document+sub darcs_xml {+ my ($fh, $repo, $cmd, $args, $dir, $file) = @_;++ make_xml($fh, $repo, $dir, $file);++ push(@$args, '--xml-output');+ darcs($fh, $repo, $cmd, $args, $dir, $file);++ finish_xml($fh);+}++# run darcs with output redirected to the specified file handle+sub darcs {+ my ($fh, $repo, $cmd, $args, $dir, $file) = @_;+ my (@darcs_argv) = ($darcs_program, $cmd, @$args);++ # push target only if there is one, otherwise darcs will get an empty param+ if ($dir || $file) {+ push(@darcs_argv, sprintf("%s%s%s", $dir, ($dir ? '/' : ''), $file));+ }++ my($pid) = fork;+ if ($pid) {+ # in the parent process+ my($status) = waitpid($pid, 0);+ die "$darcs_program exited with status $?\n" if $?;+ } elsif(defined($pid)) {+ # in the child process+ open(STDIN, '/dev/null');+ if (defined($fh)) {+ open(STDOUT, '>&', $fh)+ || die "can't dup to stdout: $!\n";+ }+ chdir "$repository_root/$repo"+ || die "chdir: $repository_root/$repo: $!\n";+ exec @darcs_argv;+ die "can't exec ".$darcs_argv[0].": $!\n";+ } else {+ # fork failed+ die "can't fork: $!\n";+ }+}++# get a directory listing as XML output+sub dir_listing {+ my ($fh, $repo, $dir) = @_;+ make_xml($fh, $repo, $dir, '');++ print $fh "<files>\n";+ my $dir_ = pristine_dir ($repo) . "/$dir";+ opendir(DH, $dir_);+ while( defined (my $file_ = readdir(DH)) ) {+ next if $file_ =~ /^\.\.?$/;+ my $file = "$dir_/$file_";+ my $secs = stat($file)->mtime;+ my $mtime = localtime($secs);+ my $ts = POSIX::strftime("%Y%m%d%H%M%S", gmtime $secs);++ my ($name, $type);++ if (-d $file) {+ ($name, $type) = (basename($file) . '/', 'directory');+ } else {+ ($name, $type) = (basename($file), 'file');+ }+ print $fh qq( <$type name="$name" modified="$mtime" ts="$ts" />\n);+ }+ closedir(DH);+ print $fh "</files>\n";++ finish_xml($fh);+}++# get a repository listing as XML output+sub repo_listing {+ my($fh) = @_;++ make_xml($fh, "", "", "");++ print $fh "<repositories>\n";+ opendir(DH, $repository_root);+ while( defined (my $name = readdir(DH)) ) {+ next if $name =~ /^\.\.?$/;+ if (-d "$repository_root/$name/_darcs") {+ print $fh qq( <repository name="$name" />\n);+ }+ }+ closedir(DH);+ print $fh "</repositories>\n";++ finish_xml($fh);+ return $fh;+}++# show an error page+sub show_error {+ my ($type, $code, $message) = @_;+ my $xml;++ # set the xslt processing arguments+ my $xslt_args = qq {+ --stringparam error-type '$type'+ --stringparam stylesheet '$stylesheet'+ };+ $xslt_args =~ s/\s+/ /gm;++ print "Status: $code $message\r\n\r\n";+ system("$xslt_program $xslt_args $xslt_errors $xml_errors");+}++# check if the requested resource has been modified since the client last+# saw it. If not send HTTP status code 304, otherwise set the Last-modified+# and Cache-control headers.+sub is_cached {+ my ($path) = @_;+ my ($stat) = stat($path);++ # stat may fail because the path was renamed or deleted but still referred+ # to by older darcs patches+ if ($stat) {+ my $last_modified = CGI::expires($stat->mtime);++ if (http('If-Modified-Since') eq $last_modified) {+ print("Status: 304 Not Modified\r\n\r\n");+ return 1;+ }++ print("Cache-control: max-age=0, must-revalidate\r\n");+ print("Last-modified: $last_modified\r\n");+ }+ return 0;+}++# safely extract a parameter from the http request. This applies a regexp+# to the parameter which should group only the appropriate parameter value+sub safe_param {+ my ($param, $regex, $default) = @_;+ my $value = CGI::Util::unescape(param($param));+ return ($value =~ $regex) ? $1 : $default;+}++# common regular expressions for validating passed parameters+my $hash_regex = qr/^([\w\-.]+)$/;+my $path_regex = qr@^([^\\!\$\^&*()\[\]{}<>`|';"?\r\n]+)$@;++# respond to a CGI request+sub respond {+ # untaint the full URL to this CGI+ my $cgi_url = CGI::Util::unescape(url());+ $cgi_url =~ $path_regex or die qq(bad url "$cgi_url");+ $cgi_url = $1;++ # untaint script_name, reasonable to expect only \w, -, /, and . in the name+ my $script_name = CGI::Util::unescape(script_name());+ $script_name =~ qr~^([\w/.\-\~]+)$~ or die qq(bad script_name "$script_name");+ $script_name = $1;++ # untaint simple parameters, which can only have chars matching \w++ my $cmd = safe_param('c', '^(\w+)$', 'browse');+ my $sort = safe_param('s', '^(\w+)$', '');++ # set the xslt processing arguments+ my $xslt_args = qq {+ --stringparam cgi-program '$script_name'+ --stringparam cgi-url '$cgi_url'+ --stringparam sort-by '$sort'+ --stringparam stylesheet '$stylesheet'+ };+ $xslt_args =~ s/\s+/ /gm;++ my ($path) = CGI::Util::unescape(path_info());+ # don't allow ./ or ../ in paths+ $path =~ s|[.]+/||g;++ # check whether we're asking for styles.css+ if ($path eq '/styles.css') {+ return if is_cached($css_styles);++ open (STYLES_CSS, $css_styles) or die qq(couldn't open "${css_styles}");+ my $size = stat($css_styles)->size;++ print "Content-length: $size\r\n";+ print "Content-type: text/css\r\n\r\n";++ while (<STYLES_CSS>) {+ print $_;+ }+ close (STYLES_CSS);+ return;+ }++ # check whether we're asking for favicon.ico+ if ($path =~ '/[\w\-]+/favicon.ico') {+ return if is_cached($favicon);++ open (FAVICON, $favicon) or die qq(couldn't open "${favicon}");+ my $size = stat($favicon)->size;++ print "Content-length: $size\r\n";+ print "Content-type: image/x-icon\r\n\r\n";++ while (<FAVICON>) {+ print $_;+ }+ close (FAVICON);+ return;+ }++ # when no repository is requested display available repositories+ if (length($path) < 2) {+ my $fh = transform($xslt_repos, $xslt_args);+ repo_listing($fh);+ return;+ }++ # don't allow any shell meta characters in paths+ $path =~ $path_regex or die qq(bad path_info "$path");+ my @path = split('/', substr($1, 1));++ # split the path into a repository, directory, and file+ my ($repo, $dir, $file, @bits) = ('', '', '');+ while (@path > 0) {+ $repo = join('/', @path);+ # check if remaining path elements refer to a repo+ if (-d "${repository_root}/${repo}/_darcs") {+ if (@bits > 1) {+ $dir = join('/', @bits[0..$#bits - 1]);+ }+ $file = $bits[$#bits];+ # check if last element of path, stored in $file, is really a dir+ if (-d (pristine_dir ($repo) . "/${dir}/${file}")) {+ $dir = ($dir ? "$dir/$file" : $file);+ $file = '';+ }+ last;+ } else {+ $repo = '';+ unshift(@bits, pop @path);+ }+ }++ # make sure the repository exists+ unless ($repo) {+ show_error('invalid-repository', '404', 'Invalid repository');+ return;+ }++ # don't generate output unless the requested path has been+ # modified since the client last saw it.+ return if is_cached(pristine_dir ($repo) . "/$dir/$file");++ # untaint patches and tags. Tags can have arbitrary values, so+ # never pass these unquoted, on pain of pain!+ my $patch = safe_param('p', $hash_regex);+ my $tag = safe_param('t', '^(.+)$');++ my @darcs_args;+ push(@darcs_args, '--match', "hash $patch") if $patch;+ push(@darcs_args, '-t', $tag) if $tag;++ # process the requested command+ if ($cmd eq 'browse') {+ my $fh = transform($xslt_browse, $xslt_args);+ dir_listing($fh, $repo, $dir);+ } elsif ($cmd eq 'patches') {+ # patches as an option is used to support "--patches"+ if (my $patches = safe_param('patches','^(.+)$')) {+ push @darcs_args, '--patches', $patches;+ }++ my $fh = transform($xslt_patches, $xslt_args);+ darcs_xml($fh, $repo, "changes", \@darcs_args, $dir, $file);+ } elsif ($cmd eq 'annotate') {+ push(@darcs_args, '--summary');++ my $creator_hash = safe_param('ch', $hash_regex);+ my $original_path = safe_param('o', $path_regex);+ my $fh = transform($xslt_annotate, $xslt_args);++ # use the creator hash and original file name when available so+ # annotations can span renames+ if ($creator_hash ne '' && $original_path ne '') {+ push(@darcs_args, '--creator-hash', $creator_hash);+ darcs_xml($fh, $repo, "annotate", \@darcs_args, '', $original_path);+ } else {+ darcs_xml($fh, $repo, "annotate", \@darcs_args, $dir, $file);+ }+ } elsif ($cmd eq 'diff') {+ push(@darcs_args, '-u');+ print "Content-type: text/plain\r\n\r\n";+ darcs(undef, $repo, "diff", \@darcs_args, $dir, $file);+ } elsif ($cmd eq 'rss') {+ push(@darcs_args, '--last', '25');++ my $fh = transform($xslt_rss, $xslt_args, "application/rss+xml");+ darcs_xml($fh, $repo, "changes", \@darcs_args, $dir, $file);+ } else {+ show_error('invalid-command', '400', 'Invalid command');+ }+}++# run a self-test when the --check argument is supplied+if ($ARGV[0] eq '--check') {+ (read_conf("css_styles", "abc") ne "abc") ||+ die "cannot read config file: $!\n";++ (`$darcs_program`) ||+ die "cannot execute darcs as '$darcs_program': $!\n";+ (`$xslt_program`) ||+ die "cannot execute xstlproc as '$xslt_program': $!\n";++ (-d $repository_root && -r $repository_root) ||+ die "cannot read repository root directory '$repository_root': $!\n";+ (-d $template_root && -r $template_root) ||+ die "cannot read template root directory '$template_root': $!\n";+ (-f $css_styles) ||+ die "cannot read css stylesheet '$css_styles': $!\n";+ (-f $xml_errors) ||+ die "cannot read error messages '$xml_errors': $!\n";++ exit 0;+}++# handle the CGI request+respond();+
@@ -0,0 +1,390 @@+<?xml version="1.0" encoding="utf-8"?>++<!--+ template for converting darcs' `annotate` output from XML to XHTML. This+ template expects the following external variables:++ cgi-program - path to the CGI executable, to place in links+ sort-by - field to sort by+ stylesheet - path to the CSS stylesheet++-->+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">+ <xsl:strip-space elements="*"/>++ <xsl:include href="common.xslt"/>++ <xsl:template match="path">+ <h1 class="target">+ annotations for <span class="path"><a href="{$cgi-program}">projects</a></span> / <span class="path"><xsl:apply-templates/></span>+ </h1>+ </xsl:template>++ <!-- patch annotate tags -->+ <xsl:template match="/darcs/patch">+ <xsl:call-template name="show-patch-info">+ <xsl:with-param name="patch" select="."/>+ <xsl:with-param name="show-comment" select="true()"/>+ <xsl:with-param name="show-author" select="true()"/>+ <xsl:with-param name="show-date" select="true()"/>+ </xsl:call-template>+ </xsl:template>+ + <xsl:template match="summary">+ <xsl:variable name="hash" select="../patch/@hash"/>+ + <table>+ <tr class="annotate">+ <th>file</th>+ <th>change</th>+ <th></th>+ <th></th>+ </tr>++ <xsl:apply-templates/>+ </table>++ <form action="{$command}" method="get">+ <p>+ <input type="submit" name="c" value="diff"/>+ <input type="hidden" name="p" value="{$hash}"/>+ </p>+ </form>+ </xsl:template>+ + <xsl:template match="modify_file">+ <xsl:variable name="file" select="text()"/>+ <xsl:variable name="hash" select="/darcs/patch/@hash" />+ + <tr class="modified-file">+ <td><xsl:value-of select="$file"/></td>++ <td class="line-count">+ <xsl:for-each select="added_lines">+ +<xsl:value-of select="@num"/>/+ </xsl:for-each>+ <xsl:for-each select="removed_lines">-<xsl:value-of select="@num"/>+ </xsl:for-each>+ lines+ </td>++ <td><a href="{$command}{$file}?c=annotate&p={$hash}">annotate</a></td>+ <td><a href="{$command}{$file}?c=patches">patches</a></td>+ </tr>+ </xsl:template>++ <xsl:template match="add_file">+ <xsl:variable name="file" select="text()"/>+ <xsl:variable name="hash" select="/darcs/patch/@hash" />+ + <tr class="add-remove-file">+ <td><xsl:value-of select="$file"/></td>+ <td>added file</td>+ <td><a href="{$command}{$file}?c=annotate&p={$hash}">annotate</a></td>+ <td><a href="{$command}{$file}?c=patches">patches</a></td>+ </tr>+ </xsl:template>++ <xsl:template match="move">+ <xsl:variable name="file" select="@to"/>+ <xsl:variable name="old-file" select="@from"/> + <xsl:variable name="hash" select="/darcs/patch/@hash" />+ + <tr class="add-remove-file">+ <td><xsl:value-of select="$file"/></td>+ <td>moved from <xsl:value-of select="$old-file"/></td>+ <td><a href="{$command}{$file}?c=annotate&p={$hash}">annotate</a></td>+ <td><a href="{$command}{$file}?c=patches">patches</a></td>+ </tr>+ </xsl:template>++ <xsl:template match="remove_file">+ <xsl:variable name="file" select="text()"/>+ <xsl:variable name="hash" select="/darcs/patch/@hash" />+ + <tr class="add-remove-file">+ <td><xsl:value-of select="$file"/></td>+ <td>removed file</td>+ <td><a href="{$command}{$file}?c=annotate&p={$hash}">annotate</a></td>+ <td><a href="{$command}{$file}?c=patches">patches</a></td>+ </tr>+ </xsl:template>++ <xsl:template match="add_directory">+ <xsl:variable name="dir" select="text()"/>+ <xsl:variable name="hash" select="/darcs/patch/@hash" />+ + <tr class="add-remove-file">+ <td><xsl:value-of select="$dir"/></td>+ <td>added directory</td>+ <td><a href="{$command}{$dir}/?c=annotate&p={$hash}">annotate</a></td>+ <td><a href="{$command}{$dir}/?c=patches">patches</a></td>+ </tr>+ </xsl:template>++ <xsl:template match="remove_directory">+ <xsl:variable name="dir" select="text()"/>+ <xsl:variable name="hash" select="/darcs/patch/@hash" />+ + <tr class="add-remove-file">+ <td><xsl:value-of select="$dir"/></td>+ <td>removed directory</td>+ <td><a href="{$command}{$dir}/?c=annotate&p={$hash}">annotate</a></td>+ <td><a href="{$command}{$dir}/?c=patches">patches</a></td>+ </tr>+ </xsl:template>+ + <!-- directory annotate templates -->+ <xsl:template match="/darcs/directory">+ <xsl:call-template name="show-patch-info">+ <xsl:with-param name="patch" select="modified/patch"/>+ </xsl:call-template>++ <table>+ <tr class="annotate">+ <th>file</th>+ <th>change</th>+ <th></th>+ <th></th>+ </tr>++ <xsl:apply-templates/>+ </table>+ </xsl:template>+ + <xsl:template match="directory/modified/modified_how">+ <xsl:variable name="hash" select="../patch/@hash" />+ <xsl:variable name="how" select="substring(text(), 0, 10)"/>+ <xsl:variable name="target" select="/darcs/@target"/>++ <!--+ annotating a directory outputs the directory itself as well as+ any contents. this ugly code checks if the matching element is+ the original directory so that the 'annotate' and 'patch' links+ don't append the directory twice.+ -->+ <xsl:variable name="last" select="//darcs/path/directory[last()]"/>+ <xsl:variable name="name" select="../../@name"/>+ <xsl:variable name="dir">+ <xsl:choose>+ <xsl:when test="$last = $name"></xsl:when>+ <xsl:otherwise><xsl:value-of select="$name"/>/</xsl:otherwise>+ </xsl:choose>+ </xsl:variable>++ <tr class="add-remove-file">+ <td><xsl:value-of select="$name"/></td>+ <td>+ <xsl:choose>+ <xsl:when test="$how = 'Dir added'">added directory</xsl:when>+ <xsl:when test="$how = 'Dir moved'">moved directory</xsl:when>+ <xsl:when test="$how = 'Dir remov'">removed directory</xsl:when>+ </xsl:choose>+ </td>+ <td>+ <a href="{$command}{$dir}?c=annotate&p={$hash}">annotate</a>+ </td>+ <td><a href="{$command}{$dir}?c=patches">patches</a></td>+ </tr>+ </xsl:template>++ <xsl:template match="directory/file/modified/modified_how">+ <xsl:variable name="file" select="../../@name"/>+ <xsl:variable name="hash" select="../patch/@hash" />+ <xsl:variable name="how" select="substring(text(), 0, 11)"/>+ + <tr class="add-remove-file">+ <td><xsl:value-of select="$file"/></td>+ <td>+ <xsl:choose>+ <xsl:when test="$how = 'File added'">added file</xsl:when>+ <xsl:when test="$how = 'File moved'">moved file</xsl:when>+ <xsl:when test="$how = 'File remov'">removed file</xsl:when>+ </xsl:choose>+ </td>+ <td><a href="{$command}{$file}?c=annotate&p={$hash}">annotate</a></td>+ <td><a href="{$command}{$file}?c=patches">patches</a></td>+ </tr>+ </xsl:template>+ + <!-- file annotate templates -->+ <xsl:template match="/darcs/file">+ <xsl:variable name="modified_how" select="modified/modified_how/text()"/>+ + <xsl:call-template name="show-patch-info">+ <xsl:with-param name="patch" select="modified/patch"/>+ </xsl:call-template>++ <!-- don't display table of changes when file's contents are modified -->+ <xsl:if test="$modified_how != 'File modified'">+ <xsl:variable name="file" select="@name"/>+ <xsl:variable name="how" select="substring($modified_how, 0, 11)"/>++ <xsl:variable name="annotate-href">+ <xsl:call-template name="make-annotate-href">+ <xsl:with-param name="hash" select="modified/patch/@hash"/>+ </xsl:call-template>+ </xsl:variable>+ + <table>+ <tr class="annotate">+ <th>file</th>+ <th>change</th>+ <th></th>+ <th></th>+ </tr>++ <tr class="add-remove-file">+ <td><xsl:value-of select="$file"/></td>+ <td>+ <xsl:choose>+ <xsl:when test="$how = 'File added'">added file</xsl:when>+ <xsl:when test="$how = 'File moved'">moved file</xsl:when>+ <xsl:when test="$how = 'File remov'">removed file</xsl:when>+ </xsl:choose>+ </td>+ <td><a href="{$annotate-href}">annotate</a></td>+ <td><a href="{$command}?c=patches">patches</a></td>+ </tr>+ </table>+ </xsl:if>++ <!-- the empty <p/> element is a Konqueror bug workaround -->+ <pre xml:space="preserve"><p/>+ <xsl:apply-templates/>+ </pre>+ </xsl:template>+ + <xsl:template match="added_line">+ <xsl:variable name="annotate-href">+ <xsl:call-template name="make-annotate-href">+ <xsl:with-param name="hash" select="preceding::modified/patch/@hash"/>+ </xsl:call-template>+ </xsl:variable>++ <xsl:variable name="annotate-name" select="preceding::modified/patch/name"/>+ <xsl:variable name="author" select="preceding::modified/patch/@author"/>+ + <a class="added-line" href="{$annotate-href}" title="added with '{$annotate-name}' by '{$author}'">+ <pre><xsl:value-of select="text()"/></pre>+ </a>+ <xsl:call-template name="check-removed-by"/>+ </xsl:template>++ <xsl:template match="normal_line">+ <xsl:variable name="annotate-href">+ <xsl:call-template name="make-annotate-href">+ <xsl:with-param name="hash" select="*/patch/@hash"/>+ </xsl:call-template>+ </xsl:variable>++ <xsl:variable name="annotate-name" select="*/patch/name"/>+ <xsl:variable name="author" select="*/patch/@author"/>++ <a class="normal-line" href="{$annotate-href}" title="last changed with '{$annotate-name}' by '{$author}'">+ <pre><xsl:value-of select="text()"/></pre>+ </a>+ <xsl:call-template name="check-removed-by"/>+ </xsl:template>++ <xsl:template match="removed_line">+ <xsl:variable name="annotate-href">+ <xsl:call-template name="make-annotate-href">+ <xsl:with-param name="hash" select="*/patch/@hash"/>+ </xsl:call-template>+ </xsl:variable>++ <xsl:variable name="annotate-name" select="*/patch/name"/>+ <xsl:variable name="author" select="*/patch/@author"/>+ + <!-- don't display removed lines when a file is removed -->+ <xsl:if test="../modified/modified_how/text() != 'File removed'">+ + <a class="removed-line" href="{$annotate-href}" title="removed with '{$annotate-name}' by '{$author}'">+ <pre><xsl:value-of select="text()"/></pre>+ </a>+ <xsl:call-template name="check-removed-by"/>+ </xsl:if>+ </xsl:template>++ <xsl:template name="check-removed-by">+ <xsl:variable name="hash" select="removed_by/patch/@hash"/>+ + <xsl:variable name="annotate-href">+ <xsl:call-template name="make-annotate-href">+ <xsl:with-param name="hash" select="$hash"/>+ </xsl:call-template>+ </xsl:variable>+ + <xsl:choose>+ <xsl:when test="$hash != ''">+ <a class="removed-by" href="{$annotate-href}">-</a>+ </xsl:when>+ </xsl:choose>++ <!-- put a newline after the hyperlink --> + <xsl:text xml:space="preserve">+ </xsl:text>+ </xsl:template>++ <xsl:template name="show-patch-info">+ <xsl:param name="patch"/>+ <xsl:param name="show-comment"/>+ <xsl:param name="show-author"/>+ <xsl:param name="show-date"/>+ + <xsl:variable name="hash" select="$patch/@hash"/>+ <xsl:variable name="name" select="$patch/name"/>+ <xsl:variable name="author" select="$patch/@author"/>+ <xsl:variable name="local_date" select="$patch/@local_date"/>+ + <h2 class="patch">+ patch "<span class="path">+ <a href="{$cgi-program}/{$repo}/?c=annotate&p={$hash}">+ <xsl:value-of select="$name"/>+ </a>+ </span>"+ </h2>++ <xsl:if test="$show-comment">+ <xsl:if test="$patch/comment">+ <h2 class="patch">+ comment "<span class="comment">+ <xsl:value-of select="$patch/comment"/>+ </span>"+ </h2>+ </xsl:if>+ </xsl:if>++ <xsl:if test="$show-author"> + <h2 class="author">+ by <span class="author">+ <xsl:value-of select="$patch/@author"/>+ </span>++ <xsl:if test="$show-date"> + on <span class="local_date">+ <xsl:value-of select="$patch/@local_date"/>+ </span>+ </xsl:if>+ </h2>+ </xsl:if>++ </xsl:template>++ <xsl:template name="make-annotate-href" xml:space="default">+ <xsl:param name="hash"/>++ <xsl:variable name="created-as" select="/darcs/*/created_as"/>+ <xsl:variable name="creator-hash" select="$created-as/patch/@hash"/>+ <xsl:variable name="original-name" select="$created-as/@original_name"/>+ + <xsl:value-of select="$command"/>?c=annotate&p=<xsl:value-of select="$hash"/>&ch=<xsl:value-of select="$creator-hash"/>&o=<xsl:value-of select="$original-name"/>+ </xsl:template>++ <!-- ignore <name>, <comment> and <modified_how> children -->+ <xsl:template match="name"/>+ <xsl:template match="comment"/>+ <xsl:template match="author"/>+ <xsl:template match="modified_how"/>+</xsl:stylesheet>
@@ -0,0 +1,110 @@+<?xml version="1.0" encoding="utf-8"?>++<!--+ template for converting a darcs current/ directory listing from XML to+ XHTML. This template expects the following external variables:++ cgi-program - path to the CGI executable, to place in links+ sort-by - field to sort by+ stylesheet - path to the CSS stylesheet++ the input XML must have the following structure, aside from what common.xslt+ expects:++ <files>+ <directory name="" modified=""/>+ <file name="" modified=""/>+ </files>+-->+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">+ <xsl:include href="common.xslt"/>++ <xsl:template match="path">+ <h1 class="target">+ files in <span class="path"><a href="{$cgi-program}">projects</a></span> / <span class="path"><xsl:apply-templates/></span>+ </h1>+ </xsl:template>+ + <xsl:template match="files">+ <table>+ <tr class="browse">+ <th>+ <a class="sort" href="{$command}?c=browse&s=name">name</a>+ <a class="revsort" href="{$command}?c=browse&s=revname"> (rev)</a>+ </th>+ <th>+ <a class="sort" href="{$command}?c=browse&s=ts">modified</a>+ <a class="revsort" href="{$command}?c=browse&s=revts"> (rev)</a>+ </th>+ <th></th>+ <th></th>+ </tr>+ + <xsl:choose>+ <xsl:when test="$sort-by = 'ts'">+ <xsl:apply-templates>+ <xsl:sort select="name(self::node())"/>+ <xsl:sort select="@ts"/>+ </xsl:apply-templates>+ </xsl:when>+ <xsl:when test="$sort-by = 'revts'">+ <xsl:apply-templates>+ <xsl:sort select="name(self::node())"/>+ <xsl:sort select="@ts" order="descending"/>+ </xsl:apply-templates>+ </xsl:when>+ <xsl:when test="$sort-by = 'name'">+ <xsl:apply-templates>+ <xsl:sort select="name(self::node())"/>+ <xsl:sort select="@name"/>+ </xsl:apply-templates>+ </xsl:when>+ <xsl:when test="$sort-by = 'revname'">+ <xsl:apply-templates>+ <xsl:sort select="name(self::node())"/>+ <xsl:sort select="@name" order="descending"/>+ </xsl:apply-templates>+ </xsl:when>+ <xsl:otherwise>+ <xsl:apply-templates>+ <xsl:sort select="name(self::node())"/>+ <xsl:sort select="@name"/>+ </xsl:apply-templates>+ </xsl:otherwise>+ </xsl:choose>+ </table>+ </xsl:template>+ + <xsl:template match="directory">+ <xsl:variable name="name" select="@name"/>+ <xsl:variable name="type" select="@type"/>+ + <tr class="directory">+ <td>+ <a href="{$command}{$name}?c=browse"><xsl:value-of select="@name"/></a>+ </td>+ <td><xsl:value-of select="@modified"/></td>+ <td>+ <a href="{$command}{$name}?c=annotate">annotate</a>+ </td>+ <td>+ <a href="{$command}{$name}?c=patches">patches</a>+ </td>+ </tr>++ <xsl:apply-templates/>+ </xsl:template>++ <xsl:template match="file">+ <xsl:variable name="name" select="@name"/>+ + <tr class="file">+ <td><xsl:value-of select="@name"/></td>+ <td><xsl:value-of select="@modified"/></td>+ <td><a href="{$command}{$name}?c=annotate">annotate</a></td>+ <td><a href="{$command}{$name}?c=patches">patches</a></td>+ </tr>++ <xsl:apply-templates/>+ </xsl:template>+</xsl:stylesheet>
@@ -0,0 +1,66 @@+<!--+ templates fragments shared by all templates. This template expects+ the following external variables:++ cgi-program - path to the CGI executable, to place in links+ stylesheet - path to the CSS stylesheet++ the input XML must have the following structure, plus any elements+ that are not common to all templates:++ <darcs repository="">+ <path>+ <element full-path=""></element>+ </path>++ ...+ + </darcs>+-->+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">+ <xsl:variable name="command">+ <xsl:value-of select="$cgi-program"/>/<xsl:value-of select="/darcs/@target"/>+ </xsl:variable>++ <xsl:variable name="repo" select="/darcs/@repository"/>+ + <xsl:template match="/darcs">+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">+ <head>+ <title>darcs repository</title>+ <link rel="stylesheet" href="{$stylesheet}"/>+ <link rel="alternate" type="application/rss+xml" title="RSS 2.0" href="{$command}?c=rss" />+ </head>++ <body>+ <xsl:apply-templates/>++ <form action="{$command}" method="get">+ <p>+ <input class="patches" type="submit" name="c" value="patches"/>+ + <a href="{$command}?c=rss">rss</a>+ + <span class="version">+ <a class="home" href="http://darcs.net/">darcs.cgi</a>+ v1.0+ </span>+ </p>+ </form>+ </body>+ </html>+ </xsl:template>++ <xsl:template match="path/directory">+ <xsl:variable name="full-path" select="@full-path"/>+ + <a href="{$cgi-program}{$full-path}?c=browse">+ <xsl:apply-templates/>+ </a> /+ </xsl:template>++ <xsl:template match="path/file">+ <xsl:apply-templates/>+ </xsl:template>+ +</xsl:stylesheet>
@@ -0,0 +1,22 @@+<!--+ HTML templates for all potential error pages. Each <error> element+ should contain all of the HTML <body> content to be output if that+ error occurs+-->+<errors>+ <error type="invalid-command" title="Invalid command">+ <h1>Invalid command</h1>++ <p>+ The requested command is invalid.+ </p>+ </error>++ <error type="invalid-repository" title="Invalid repository">+ <h1>Invalid repository</h1>++ <p>+ The requested repository does not exist on this server.+ </p>+ </error>+</errors>
@@ -0,0 +1,36 @@+<?xml version="1.0" encoding="utf-8"?>++<!--+ template for converting selected content of errors.xml XML to XHTML. This+ template expects the following external variables:++ error-type - the type of error+ stylesheet - path to the CSS stylesheet++ the input XML must have the following structure++ <errors>+ <error type="" title="">+ ...+ </error>+ </errors>+-->+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">+ <xsl:template match="error[@type = $error-type]">+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">+ <head>+ <title>+ <xsl:value-of select="@title"/>+ </title>+ <link rel="stylesheet" href="{$stylesheet}"/>+ </head>++ <body>+ <xsl:copy-of select="*"/>+ </body>+ </html>+ </xsl:template>++ <!-- ignore errors that don't match -->+ <xsl:template match="error"/>+</xsl:stylesheet>
@@ -0,0 +1,125 @@+<?xml version="1.0" encoding="utf-8"?>++<!--+ template for converting darcs' `changes` output from XML to XHTML. This+ template expects the following external variables:++ cgi-program - path to the CGI executable, to place in links+ sort-by - field to sort by+ stylesheet - path to the CSS stylesheet++ the input XML must have the following structure, aside from what common.xslt+ expects:++ <changelog>+ <patch author="" date="" localdate="" inverted="" hash="">+ <name></name>+ </patch>+ </changelog>+-->+<xsl:stylesheet version="1.0"+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"+ xmlns:str="http://exslt.org/strings">+ + <xsl:include href="common.xslt"/>++ <xsl:template match="path">+ <h1 class="target">+ patches applied to <span class="path"><a href="{$cgi-program}">projects</a></span> / <span class="path"><xsl:apply-templates/></span>+ </h1>+ </xsl:template>+ + + <xsl:template match="changelog">+ <table>+ <tr class="patches">+ <th>+ <a class="sort" href="{$command}?c=patches&s=name">name</a>+ <a class="revsort" href="{$command}?c=patches&s=revname"> (rev)</a>+ </th>+ <th>+ <a class="sort" href="{$command}?c=patches&s=date">date</a>+ <a class="revsort" href="{$command}?c=patches&s=revdate"> (rev)</a>+ </th>+ <th>+ <a class="sort" href="{$command}?c=patches&s=author">author</a>+ <a class="revsort" href="{$command}?c=patches&s=revauthor"> (rev)</a>+ </th>+ <th>+ </th>+ </tr>++ <xsl:choose>+ <xsl:when test="$sort-by = 'name'">+ <xsl:apply-templates>+ <xsl:sort select="name"/>+ </xsl:apply-templates>+ </xsl:when>+ <xsl:when test="$sort-by = 'revname'">+ <xsl:apply-templates>+ <xsl:sort select="name" order="descending"/>+ </xsl:apply-templates>+ </xsl:when>+ <xsl:when test="$sort-by = 'date'">+ <xsl:apply-templates>+ <xsl:sort select="@date"/>+ </xsl:apply-templates>+ </xsl:when>+ <xsl:when test="$sort-by = 'revdate'">+ <xsl:apply-templates>+ <xsl:sort select="@date" order="descending"/>+ </xsl:apply-templates>+ </xsl:when>+ <xsl:when test="$sort-by = 'author'">+ <xsl:apply-templates>+ <xsl:sort select="@author"/>+ </xsl:apply-templates>+ </xsl:when>+ <xsl:when test="$sort-by = 'revauthor'">+ <xsl:apply-templates>+ <xsl:sort select="@author" order="descending"/>+ </xsl:apply-templates>+ </xsl:when>+ <xsl:otherwise>+ <!-- use the repository's natural order -->+ <xsl:apply-templates/>+ </xsl:otherwise>+ </xsl:choose>+ </table>+ </xsl:template>++ <xsl:template match="patch">+ <xsl:variable name="author" select="@author"/>+ <xsl:variable name="hash" select="@hash"/>+ <xsl:variable name="name" select="name"/>+ <xsl:variable name="repo" select="/darcs/@repository"/>++ <xsl:variable name="created-as" select="/darcs/changelog/created_as"/>+ <xsl:variable name="creator-hash" select="$created-as/patch/@hash"/>+ <xsl:variable name="original-name" select="$created-as/@original_name"/>++ <xsl:variable name="annotate-href">+ <xsl:value-of select="$command"/>?c=annotate&p=<xsl:value-of select="$hash"/>++ <xsl:if test="$creator-hash">&ch=<xsl:value-of select="$creator-hash"/></xsl:if>+ <xsl:if test="$original-name">&o=<xsl:value-of select="$original-name"/></xsl:if>+ </xsl:variable>+ + <tr class="patch">+ <td><a href="{$annotate-href}"><xsl:value-of select="name"/></a></td>+ <td><xsl:value-of select="@local_date"/></td>+ <td>+ <a href="mailto:{$author}"><xsl:value-of select="$author"/></a>+ </td>+ <td>+ <a href="{$cgi-program}/{$repo}/?c=annotate&p={$hash}">detail</a>+ </td>+ </tr>+ <xsl:apply-templates/>+ </xsl:template>++ <!-- ignore <created_as>, <name> and <comment> children of <patch> -->+ <xsl:template match="created_as"/>+ <xsl:template match="name"/>+ <xsl:template match="comment"/>+</xsl:stylesheet>
@@ -0,0 +1,59 @@+<?xml version="1.0" encoding="utf-8"?>++<!--+ template for displaying a list of available repositories, used when a+ CGI request has no repository path information. This template expects+ the following external variables:++ cgi-program - path to the CGI executable, to place in links+ stylesheet - path to the CSS stylesheet++ the input XML must have the following structure:++ <darcs repository="">+ <repositories>+ <repository name=""/>+ </repositories>+ </darcs>+-->+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">+ <xsl:variable name="command">+ <xsl:value-of select="$cgi-program"/>/<xsl:value-of select="/darcs/@target"/>+ </xsl:variable>+ + <xsl:template match="/darcs">+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">+ <head>+ <title>darcs repository viewer</title>+ <link rel="stylesheet" href="{$stylesheet}"/>+ </head>++ <body>+ <h1 class="target">Available Repositories</h1>++ <table>+ <xsl:apply-templates/>+ </table>+ </body>+ </html>+ </xsl:template>++ <xsl:template match="repositories">+ <xsl:apply-templates>+ <xsl:sort select="name(self::node())"/>+ <xsl:sort select="@name"/>+ </xsl:apply-templates>+ </xsl:template>++ <xsl:template match="repositories/repository">+ <xsl:variable name="repository" select="@name"/>++ <tr>+ <td>+ <a href="{$cgi-program}/{$repository}/?c=browse">+ <xsl:value-of select="$repository"/>+ </a>+ </td>+ </tr>+ </xsl:template>+</xsl:stylesheet>
@@ -0,0 +1,92 @@+<?xml version="1.0" encoding="utf-8"?>++<!--+ template for converting darcs' `changes` output from XML to RSS. This+ template expects the following external variables:++ cgi-url - full URL to the CGI executable, to place in links++ the input XML must have the following structure:++ <darcs repository="">+ <changelog>+ <patch author="" date="" localdate="" inverted="" hash="">+ <name></name>+ <comment></comment>+ </patch>+ </changelog>+ </darcs>+-->+<xsl:stylesheet version="1.0"+ exclude-result-prefixes="str"+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"+ xmlns:str="http://exslt.org/strings">++ <xsl:variable name="repo" select="/darcs/@repository"/>++ <xsl:variable name="command">+ <xsl:value-of select="$cgi-url"/>/<xsl:value-of select="/darcs/@target"/>+ </xsl:variable>++ <xsl:template match="changelog">+ <rss version="2.0">+ <channel>+ <title>changes to <xsl:value-of select="$repo"/></title>+ <link><xsl:value-of select="$command"/></link>+ <description>+ Recent patches applied to the darcs repository named+ "<xsl:value-of select='$repo'/>".+ </description>++ <xsl:apply-templates>+ <xsl:sort select="@date"/>+ </xsl:apply-templates>+ </channel>+ </rss>+ </xsl:template>++ <xsl:template match="patch">+ <xsl:variable name="hash" select="@hash"/>++ <xsl:variable name="created-as" select="/darcs/changelog/created_as"/>+ <xsl:variable name="creator-hash" select="$created-as/patch/@hash"/>+ <xsl:variable name="original-name" select="$created-as/@original_name"/>++ <xsl:variable name="annotate-href">+ <xsl:value-of select="$command"/>?c=annotate&p=<xsl:value-of select="$hash"/>++ <xsl:if test="$creator-hash">&ch=<xsl:value-of select="$creator-hash"/></xsl:if>+ <xsl:if test="$original-name">&o=<xsl:value-of select="$original-name"/></xsl:if>+ </xsl:variable>++ <xsl:variable name="date-nodes" select="str:tokenize(@local_date, ' ')"/>+ <xsl:variable name="rfc-date">+ <xsl:value-of select="$date-nodes[1]"/>+ <xsl:value-of select="', '"/>+ <xsl:if test="$date-nodes[3] < 10">0</xsl:if>+ <xsl:value-of select="$date-nodes[3]"/>+ <xsl:value-of select="' '"/>+ <xsl:value-of select="$date-nodes[2]"/>+ <xsl:value-of select="' '"/>+ <xsl:value-of select="$date-nodes[6]"/>+ <xsl:value-of select="' '"/>+ <xsl:value-of select="$date-nodes[4]"/>+ <xsl:value-of select="' '"/>+ <xsl:value-of select="$date-nodes[5]"/>+ </xsl:variable>++ <item>+ <title><xsl:value-of select="name"/></title>+ <link><xsl:value-of select="$annotate-href"/></link>+ <author><xsl:value-of select="@author"/></author>+ <description><xsl:value-of select="comment"/></description>+ <pubDate><xsl:value-of select="$rfc-date"/></pubDate>+ </item>+ </xsl:template>++ <!-- ignore <path> <created_as>, <name> and <comment> children of <patch> -->+ <xsl:template match="path"/>+ <xsl:template match="created_as"/>+ <xsl:template match="name"/>+ <xsl:template match="comment"/>+</xsl:stylesheet>
@@ -0,0 +1,91 @@+/**+ stylesheet for darcs repository browser+**/++a:link, a:visited {+ color: blue;+ text-decoration: none;+}++a:hover { text-decoration: underline; }+a.sort { color: black; }+a.revsort { + color: black;+ font-size: 75%;+}+a.home { color: #9292C9; }++body { margin: 10px 10px 10px 10px; }++h1.target {+ font-size: 150%;+ font-weight: bold;+}++h2.patch {+ font-size: 125%;+ font-weight: bold;+ margin-top: -10px;+ padding-left: 10px;+}++h2.author {+ font-size: smaller;+ margin-top: -10px;+ font-weight: bold;+ padding-left: 30px;+}++input.patches { margin-right: 20px; }+label.tag { margin-right: 5px; }++span.path, span.path > a { color: #2A2A6B; }++span.version {+ color: #9292C9; + margin-left: 75px;+}++span.comment {+ color: #2A2A6B;+ white-space: pre;+}++th,td {+ text-align: left;+ padding: 5px 10px 5px 10px; +}++tr.annotate { background-color: #FFAAAA; }+tr.browse { background-color: #FFFF99; }+tr.patches { background-color: #AAFFAA; }+tr.directory { background-color: #CFCFCF; }+tr.file, tr.patch, tr.modified-file { background-color: #DCDCDA; }+tr.modified-file, tr.add-remove-file { background-color: #DCDCDA; }++pre {+ margin: 0 0 0 0;+}++a.added-line {+ color: #00AA00;+ float: left;+}++a.normal-line {+ color: #000000;+ float: left;+}++a.removed-line {+ color: #CC0000;+ float: left;+ text-decoration: line-through;+}++a.removed-by {+ clear: right;+ color: #CC0000;+ float: right;+ font-size: 125%;+}
@@ -0,0 +1,263 @@+#! /bin/bash++DIRNAME=`dirname "${0}"`+if [ "${DIRNAME:0:1}" = "/" ] ; then+ DARCSPACKAGEDIR="${DIRNAME}"+else+ DARCSPACKAGEDIR="${PWD}/${DIRNAME}"+fi++# If the DARCSPACKAGEDIR assignment above doesn't work for some funny reason, +# you could set these variables by hand. Or fix the script to work +# automatically and submit a patch.++# Should be set to the full Cygwin path to the directory containing the +# putty executables.+putty_binary_dir="${DARCSPACKAGEDIR}"+# Should be set to the full Cygwin path to the directory containing the +# Windows binary "darcs.exe".+darcs_binary_dir="$putty_binary_dir"+# Should be set to the full Cygwin path to the Windows binary+# "darcs.exe".+darcs_binary="${darcs_binary_dir}/realdarcs.exe"++#---------------------------------------------------------------------+# Darcs Wrapper for Cygwin+#+# A Bash script that allows Cywin paths on the command line when using+# a version of Darcs compiled for Windows. Darcs will still use still+# Windows paths internally.+#+#---------------------------------------------------------------------+# Usage+# +# Edit this file and set the variables above. Then, rename this +# script to "darcs" and put it in your PATH somewhere before the +# original binary.+#+# Darcs needs to launch itself for some operations and so the original+# binary needs to be in your Windows PATH. Do not rename it.+#+#---------------------------------------------------------------------+# Known Issues+#+# This script is just a stopgap measure. Things don't work perfectly.+# We really need a Cygwin build of Darcs.+#+# No path conversion is performed on:+# - Any preferences set with "setpref"+# - The "COMMAND" argument to "darcs trackdown"+#+# When Darcs launches external programs, it uses a Windows system call+# to do so. This means you may not be able to run "hash bang" scripts+# directly. For example, to run the Bash script "myscript", you'll+# have to tell Darcs to run "bash myscript".+#+# --------------------------------------------------------------------+++PATH="$putty_binary_dir:$darcs_binary_dir:${PATH}"++debug=false++cmd="$1"++# Print each argument to stderr on a separate line. Then exit.+function die() {+ local line+ for line in "$@"; do+ echo "$line" > /dev/stderr+ done+ exit 2+}++# Make sure 'darcs_binary_dir' is set.+if [ ! -d "$darcs_binary_dir" ]; then+ die "Please edit this script and set the 'darcs_binary_dir' variable" \+ "to refer to a valid directory." \+ " script path = '$0'" \+ " darcs_binary_dir = '$darcs_binary_dir'"+fi++# Special case for when the first argument is an option.+if expr match "$cmd" '-' > /dev/null; then+ if $debug; then+ # echo "SIMPLE CASE:"+ for arg in "$@"; do+ echo " arg = '$arg'"+ done+ else+ # echo about to exec -a darcs "$darcs_binary" "$@"+ exec -a darcs "$darcs_binary" "$@"+ fi+fi++# Shift off the darcs command name+shift++function is_opaque_opt() {+ local opt+ for opt in "${opaque_binary_opts[@]}"; do+ if [ "$opt" == "$1" ]; then+ return 0+ fi+ done+ return 1+}++function is_file_opt() {+ local opt+ for opt in "${file_binary_opts[@]}"; do+ if [ "$opt" == "$1" ]; then+ return 0+ fi+ done+ return 1+}++# Options are not dealt with in a command-specific way. AFAIK, Darcs+# doesn't use the same option in two different ways, so we should be+# fine.++# List of "opaque" binary options. These are options where we don't+# treat the option argument like a file.+declare -a opaque_binary_opts=( \+ '--repo-name' \+ '--to-match' '--to-patch' '--to-tag' \+ '--from-match' '--from-patch' '--from-tag' \+ '-t' '--tag' '--tags' '--tag-name' \+ '-p' '--patch' '--patches' \+ '-m' '--patch-name' \+ '--matches' '--match' \+ '--token-chars' \+ '-A' '--author' '--from' '--to' '--cc' \+ '--sign-as' '--creator-hash' \+ '--last' '--diff-opts' \+ '-d' '--dist-name' \+ '--log-file' \+ '--apply-as' \+ )++# List of binary options that take file arguments that need to be converted.+declare -a file_binary_opts=( \+ '--repodir' '--repo' '--sibling' \+ '--context' \+ '--logfile' '-o' '--output' \+ '--external-merge' \+ '--sign-ssl' '--verify' '--verify-ssl' \+ )++# --------------------------------------------------------------------+# The three command categories. We only use the first one, but the+# others are listed to make sure we've covered everything. Luckily,+# there aren't any commands that have some args that need to be+# converted and some that don't.++# Commands whose arguments are file paths that need to be translated.+cmds_convert_nonoption_args='|get|put|pull|push|send|apply'++# Commands who's arguments should be left alone. File paths that+# refer to files in the repo should NOT be converted because they+# are relative paths, which Darcs will handle just fine. Cygwin+# sometimes makes them absolute paths, which confuses Darcs.+#cmds_no_convert_nonoption_paths='|add|remove|mv|replace|record|whatsnew|changes|setpref|trackdown|amend-record|revert|diff|annotate'++# Commands that don't accept non-option arguments+#cmds_no_nonoption_args='|initialize|tag|check|optimize|rollback|unrecord|unpull|resolve|dist|repair'++# See if we need to convert the non-option args for the current+# command. This matches some prefix of one of the commands in the+# list. The match may not be unambiguous, we can rely on Darcs to+# deal with that correctly.+if expr match "$cmds_convert_nonoption_args" ".*|$cmd" > /dev/null; then+ convert_nonoption_args=true+else+ convert_nonoption_args=false+fi++function convert_path() {+ # echo "converting path ${*} ..." >> /tmp/log+ if expr match "$1" '[-@._A-Za-z0-9]*:' > /dev/null; then+ # Some sort of URL or remote ssh pathname ("xxx:/")+ echo "$1"+ # echo "converting path ${*} ... to ${1}" >> /tmp/log+ elif [ "$1" == '.' ]; then+ # Compensate for stupid 'cygpath' behavior.+ echo '.'+ # echo "converting path ${*} ... to ." >> /tmp/log+ else+ cygpath -wl -- "$1"+ # echo "converting path ${*} ... to `cygpath -wl -- ${1}`" >> /tmp/log+ fi+}++declare -a params=("$cmd")++num_nonoption_args=0++while [ $# -gt 0 ]; do+ arg=$1+ shift+ if expr match "$arg" '-' > /dev/null; then+ # It's an option. Check to see if it's an opaque binary option.++ if expr match "$arg" '.*=' > /dev/null; then+ # The option has an '=' in it.+ opt=`expr match "$arg" '\([^=]*\)'`+ opt_arg=`expr match "$arg" '[^=]*=\(.*\)'`+ if is_opaque_opt "$opt"; then+ true;+ elif is_file_opt "$opt"; then+ opt_arg=`convert_path "$opt_arg"`+ else+ die "darcs-wrapper: I don't think '$opt' accepts an argument." \+ "[ If it does, then there is a bug in the wrapper script. ]"+ fi+ params[${#params[*]}]="$opt=$opt_arg"++ else+ # The option doesn't have an '='+ opt="$arg"+ if is_opaque_opt "$opt"; then+ if [ $# -eq 0 ]; then+ die "darcs-wrapper: I think '$arg' requires an argument." \+ "[ If it doesn't, then there is a bug in the wrapper script. ]"+ fi+ opt_arg="$1"+ shift+ params[${#params[*]}]="$opt"+ params[${#params[*]}]="$opt_arg"+ elif is_file_opt "$opt"; then+ if [ $# -eq 0 ]; then+ die "darcs-wrapper: I think '$arg' requires an argument." \+ "[ If it doesn't, then there is a bug in the wrapper script. ]"+ fi+ opt_arg=`convert_path "$1"`+ shift+ params[${#params[*]}]="$opt"+ params[${#params[*]}]="$opt_arg"+ else+ params[${#params[*]}]="$opt"+ fi+ fi++ else+ if $convert_nonoption_args; then+ arg=`convert_path "$arg"`+ fi+ params[${#params[*]}]="$arg"+ (( num_nonoption_args += 1 ))+ fi+done++# DEBUG+if $debug; then+ echo "ARGS:"+ for arg in "${params[@]}"; do+ echo " arg = '$arg'"+ done+else+ # echo about to exec -a darcs "$darcs_binary" "${params[@]}"+ exec -a darcs "$darcs_binary" "${params[@]}"+fi+
@@ -0,0 +1,52 @@+#-*- mode: shell-script;-*-++# darcs command line completion.+# Copyright 2002 "David Roundy" <droundy@abridgegame.org>+#+have darcs &&+_darcs()+{+ local cur+ cur=${COMP_WORDS[COMP_CWORD]}++ COMPREPLY=()++ if (($COMP_CWORD == 1)); then+ COMPREPLY=( $( darcs --commands | grep "^$cur" ) )+ return 0+ fi++ local IFS=$'\n' # So that the following "command-output to array" operation splits only at newlines, not at each space, tab or newline.+ COMPREPLY=( $( darcs ${COMP_WORDS[1]} --list-option | grep "^${cur//./\\.}") )++ # Then, we adapt the resulting strings to be reusable by bash. If we don't+ # do this, in the case where we have two repositories named+ # ~/space in there-0.1 and ~/space in there-0.2, the first completion will+ # give us:+ # bash> darcs push ~/space in there-0.+ # ~/space in there-0.1 ~/space in there-0.2+ # and we have introduced two spaces in the command line (if we try to+ # recomplete that, it won't find anything, as it doesn't know anything+ # starting with "there-0.").+ # printf %q will gracefully add the necessary backslashes.+ #+ # Bash also interprets colon as a separator. If we didn't handle it+ # specially, completing http://example.org/repo from http://e would + # give us:+ # bash> darcs pull http:http://example.org/repo+ # An option would be to require the user to escape : as \: and we+ # would do the same here. Instead, we return only the part after+ # the last colon that is already there, and thus fool bash. The+ # downside is that bash only shows this part to the user.+ local i=${#COMPREPLY[*]}+ local colonprefixes=${cur%"${cur##*:}"}+ while [ $((--i)) -ge 0 ]; do+ COMPREPLY[$i]=`printf %q "${COMPREPLY[$i]}"`++ COMPREPLY[$i]=${COMPREPLY[$i]#"$colonprefixes"} + done+ return 0++}+[ "$have" ] && complete -F _darcs -o default darcs+
@@ -0,0 +1,78 @@+#!/usr/bin/perl++use strict;+use warnings;++# A script to update the status of an issue in a Roundup bug tracker+# based on the format of a darcs patch name.+# It is intended to be run from a darcs posthook.++# The format we look for is:+# resolved issue123 +# in the first line of the patch. ++use Getopt::Long;++use MIME::Lite;+use XML::Simple;++unless ($ENV{DARCS_PATCHES_XML}) {+ die "DARCS_PATCHES_XML was expected to be set in the environment, but was not found. + Are you running this from a Darcs 2.0 or newer posthook?"+}++my $xml = eval { XMLin($ENV{DARCS_PATCHES_XML}); };+die "hmmm.. we couldn't parse your XML. The error was: $@" if $@;++# $xml structure returned looks like this: +# 'patch' => {+# 'resolved issue123: adding t.t' => {+# 'hash' => '20080215033723-20bb4-54f935f89817985a3e98f3de8e8ac9dad5e8e0e5.gz',+# 'inverted' => 'False',+# 'date' => '20080215033723',+# 'author' => 'Mark Stosberg <mark@summersault.com>',+# 'local_date' => 'Thu Feb 14 22:37:23 EST 2008'+# },+# 'some other patch' => { ... }, ++for my $patch_name (keys %{ $xml->{patch} }) {+ my $issue_re = qr/resolved? \s+ (issue\d+)/msxi;++ next unless ($patch_name =~ $issue_re);+ my $issue = $1;+ my $patch = $xml->{patch}{$patch_name};++ # Using the Command Line would be a simpler alternative. + # my $out = `roundup-admin -i /var/lib/roundup/trackers/darcs set $issue status=resolved`;+ # warn "unexpected output: $out" if $out;++ my $author = $patch->{author};+ # If the Author name contains an @ sign, we take it to be an e-mail address.+ # Otherwise, we default to darcs-devel as the sender. + my $email = ($author =~ m/\@/) ? $author : 'darcs-devel@darcs.net';++ my $comment = $patch->{comment} ? "\n$patch->{comment}" : '';++ my $patch_name_minus_status = $patch_name; + $patch_name_minus_status =~ s/$issue_re(:?\s?)//;++ # Each patches can potentially update the status of a different issue, so generates a different e-mail+ my $msg = MIME::Lite->new(+ From => $email, + To =>'bugs@darcs.net',+ #To =>'mark@stosberg.com',+ Subject =>"[$issue] [status=resolved]",+ Type =>'text/plain',+ Data => qq!The following patch updated the status of $issue to be resolved:++* $patch_name $comment++!+ );+ $msg->send;+ # An alternative to actually sending, for debugging. + # use File::Slurp;+ # write_file("msg-$patch->{hash}.out",$msg->as_string);+}++
@@ -0,0 +1,126 @@+#!/usr/bin/perl++use strict;+use File::Temp qw/ tempdir tempfile /;++# this is a sample cgi script to accept darcs patches via POST+# it simply takes patches and sends them using sendmail or+# places them in a Maildir style mailbox.++my $tmp_dir; # temporary directory, when placing patches to maildir+ # files are linked from $tmp_dir to $maildir+$tmp_dir = "/tmp";++# target email addresses--leave blank to use To: header in patch contents.+my $target_email;++# target repository for patch testing. Leave blank to use DarcsURL header+# in patch contents.+my $target_repo;++my $sendmail_cmd; # command to send patches with+$sendmail_cmd = "/usr/sbin/sendmail -i -t $target_email";++my $maildir; # maildir to put patches to, replace sendmail+#$maildir = "/tmp/maildir";++my $patch_test_cmd; # command to test patches with+$patch_test_cmd = "darcs apply --dry-run --repodir 'TARGETREPO' 'TARGETPATCH'";++my $repo_get_cmd; # command to get testing repo+ # used only when $target_repo is blank+$repo_get_cmd = "darcs get --lazy --repodir 'TARGETDIR' 'TARGETREPO'";+++sub error_page {+ my ($m) = @_;+ print "Status: 500 Error accepting patch\n";+ print "Content-Type: text/plain\n\n";+ print($m || "There was an error processing your request");+ print "\n";+ exit 0;+}++sub success_page {+ print "Content-Type: text/plain\n\n";+ print "Thank you for your contribution!\n";+ exit 0;+}+++if ($ENV{CONTENT_TYPE} eq 'message/rfc822') {+ my $m = start_message() or error_page("could not create temporary file");+ my $fh = $m->{fh};+ my ($totalbytes, $bytesread, $buffer);+ do {+ $bytesread = read(STDIN, $buffer, 1024);+ print $fh $buffer;+ $totalbytes += $bytesread;+ } while ($bytesread);+ my $r = end_message($m);+ $r ? error_page($r) : success_page();+} elsif ($ENV{CONTENT_TYPE}) {+ error_page("invalid content type, I expect something of message/rfc822");+} else {+ error_page("This url is for accepting darcs patches.");+}++++sub maildir_file {+ my ($tmp_file) = @_;+ my $base_name = sprintf("patch-%d-%d-0000", $$, time());+ my $count = 0;+ until (link("$tmp_file", "$maildir/$base_name")) {+ $base_name =~ s/-(\d+)$/"-" . (1 + $1)/e;+ return undef if $count++ > 100;+ }+ return "$maildir/$base_name";+}++sub start_message {+ my ($fh, $fname) = tempfile("$tmp_dir/dpatch".'X'x8, UNLINK => 1) or+ return undef;+ return { fh => $fh, filename => $fname };+}++sub end_message {+ my ($m) = @_;+ close $m->{fh} or return "$!: $m->{filename} - Could not close filehandle";++ unless ($target_repo) {+ # Look for DarcsURL header+ my $darcsurl;+ open(MF,$m->{filename}) or return "$!: $m->{filename} - Could not open file";+ while (<MF>) {+ if (/^DarcsURL: (.+)$/) {+ $darcsurl = $1;+ last;+ }+ }+ close(MF);+ return "Could not find DarcsURL header" unless $darcsurl;++ my $test_dir = tempdir(CLEANUP => 1).'/repo' or+ return "$!: Could not create test directory";+ $repo_get_cmd =~ s/TARGETDIR/$test_dir/;+ $repo_get_cmd =~ s/TARGETREPO/$darcsurl/;+ system("$repo_get_cmd >/dev/null 2>/dev/null") == 0 or+ return "Could not get target repo: '$repo_get_cmd' failed";+ $target_repo = $test_dir;+ }+ $patch_test_cmd =~ s/TARGETREPO/$target_repo/;+ $patch_test_cmd =~ s/TARGETPATCH/$m->{filename}/;+ system("$patch_test_cmd >/dev/null 2>/dev/null") == 0 or+ return "Patch is not valid: '$patch_test_cmd' failed";++ if ($maildir) {+ maildir_file("$m->{filename}") or+ return "$!: Could not create a new file in maildir";+ } else {+ system("$sendmail_cmd < '$m->{filename}'") == 0 or+ return "$!: Could not send mail";+ }++ return 0;+}
@@ -0,0 +1,648 @@+Name: darcs-beta+version: 2.2.98.1+License: GPL+License-file: COPYING+Author: David Roundy <droundy@darcs.net>, <darcs-users@darcs.net>+Maintainer: <darcs-users@darcs.net>++Stability: Experimental+Category: Development+Synopsis: a distributed, interactive, smart revision control system+Description: Darcs is a free, open source revision control+ system. It is:+ .+ * Distributed: Every user has access to the full+ command set, removing boundaries between server and+ client or committer and non-committers.+ .+ * Interactive: Darcs is easy to learn and efficient to+ use because it asks you questions in response to+ simple commands, giving you choices in your work+ flow. You can choose to record one change in a file,+ while ignoring another. As you update from upstream,+ you can review each patch name, even the full "diff"+ for interesting patches.+ .+ * Smart: Originally developed by physicist David+ Roundy, darcs is based on a unique algebra of+ patches.+ .+ This smartness lets you respond to changing demands+ in ways that would otherwise not be possible. Learn+ more about spontaneous branches with darcs.+Homepage: http://darcs.net/++Build-Type: Custom+Cabal-Version: >= 1.6+Tested-with: GHC==6.8.2++extra-source-files:+ -- C headers+ src/*.h src/Crypt/sha2.h+ src/win32/send_email.h src/win32/sys/mman.h++ -- TODO: factor out these standalone executables to other sections+ src/preproc.hs, src/unit.lhs,++ -- The contrib directory would make a sensible 'darcs-contrib' package+ contrib/_darcs.zsh, contrib/darcs_completion,+ contrib/cygwin-wrapper.bash, contrib/update_roundup.pl, contrib/upload.cgi,+ contrib/cgi/darcs.cgi.in, contrib/cgi/cgi.conf.in, contrib/cgi/README.in+ contrib/cgi/xslt/*.xslt, contrib/cgi/xslt/*.xml, contrib/cgi/xslt/*.css,++ -- documentation files+ src/best_practices.tex, src/building_darcs.tex, src/configuring_darcs.tex,+ src/features.tex, src/gpl.tex, src/switching.tex,+ contrib/cgi/README.in++ README++ -- release data+ release/distributed-version, release/distributed-context++ -- testsuite+ Distribution/ShellHarness.hs+ tests/repos/*.tgz tests/repos/README+ tests/*.sh+ tests/network/*.sh+ tests/lib+ tests/example_binary.png+ bugs/*.sh++source-repository head+ type: darcs+ location: http://darcs.net/++flag curl+ description: Use libcurl for HTTP support.++flag curl-pipelining+ description: Use libcurl's HTTP pipelining.+ default: False+ --TODO: needs Cabal ticket #342 to allow default True+ -- and decide on this automatically++flag http+ description: Use the pure Haskell HTTP package for HTTP support.++-- SUNSET: this should be mandatory after the 2009-07 release is out+flag bytestring+ description: Use the external bytestring package.++flag zlib+ description: Use the external zlib binding package.+ default: True++-- SUNSET: this should be mandatory after the 2009-07 release is out+flag utf8-string+ description: Use the external utf8-string binding package.++flag terminfo+ description: Use the terminfo package for enhanced console support.++flag type-witnesses+ description: Use GADT type witnesses.+ default: False++flag color+ description: Use ansi color escapes.++flag mmap+ description: Compile with mmap support.++flag base3++flag test+ description: Compile unit tests (requires QuickCheck >= 2.1.0.0).+ default: False++Executable witnesses+ main-is: witnesses.hs+ hs-source-dirs: src+ include-dirs: src+ cpp-options: -DGADT_WITNESSES=1+ -- FIXME...+ c-sources: src/atomic_create.c+ src/fpstring.c+ src/c_compat.c+ src/maybe_relink.c+ src/umask.c+ src/Crypt/sha2.c+ if !flag(type-witnesses)+ buildable: False++ if !flag(zlib)+ extra-libraries: z++ if os(windows)+ hs-source-dirs: src/win32+ include-dirs: src/win32+ other-modules: CtrlC+ System.Posix+ System.Posix.Files+ System.Posix.IO+ cpp-options: -DWIN32+ c-sources: src/win32/send_email.c++-- ----------------------------------------------------------------------+-- darcs library+-- ----------------------------------------------------------------------++Library+ hs-source-dirs: src+ include-dirs: src++ exposed-modules: CommandLine+ Crypt.SHA256+ Darcs.ArgumentDefaults+ Darcs.Arguments+ Darcs.Bug+ Darcs.CheckFileSystem+ Darcs.ColorPrinter+ Darcs.Commands+ Darcs.Commands.Add+ Darcs.Commands.AmendRecord+ Darcs.Commands.Annotate+ Darcs.Commands.Apply+ Darcs.CommandsAux+ Darcs.Commands.Changes+ Darcs.Commands.Check+ Darcs.Commands.Convert+ Darcs.Commands.Diff+ Darcs.Commands.Dist+ Darcs.Commands.Get+ Darcs.Commands.GZCRCs+ Darcs.Commands.Help+ Darcs.Commands.Init+ Darcs.Commands.MarkConflicts+ Darcs.Commands.Move+ Darcs.Commands.Optimize+ Darcs.Commands.Pull+ Darcs.Commands.Push+ Darcs.Commands.Put+ Darcs.Commands.Record+ Darcs.Commands.Remove+ Darcs.Commands.Repair+ Darcs.Commands.Replace+ Darcs.Commands.Revert+ Darcs.Commands.Rollback+ Darcs.Commands.Send+ Darcs.Commands.SetPref+ Darcs.Commands.Show+ Darcs.Commands.ShowAuthors+ Darcs.Commands.ShowBug+ Darcs.Commands.ShowContents+ Darcs.Commands.ShowFiles+ Darcs.Commands.ShowIndex+ Darcs.Commands.ShowRepo+ Darcs.Commands.ShowTags+ Darcs.Commands.Tag+ Darcs.Commands.TrackDown+ Darcs.Commands.TransferMode+ Darcs.Commands.Unrecord+ Darcs.Commands.Unrevert+ Darcs.Commands.WhatsNew+ Darcs.Compat+ Darcs.Diff+ Darcs.Email+ Darcs.External+ Darcs.FilePathMonad+ Darcs.Flags+ Darcs.Global+ Darcs.Gorsvet+ Darcs.Hopefully+ Darcs.IO+ Darcs.Lock+ Darcs.Match+ Darcs.Ordered+ Darcs.Patch+ Darcs.Patch.Apply+ Darcs.Patch.Bundle+ Darcs.Patch.Check+ Darcs.Patch.Choices+ Darcs.Patch.Commute+ Darcs.Patch.Core+ Darcs.Patch.Depends+ Darcs.Patch.FileName+ Darcs.Patch.Info+ Darcs.Patch.Match+ Darcs.Patch.MatchData+ Darcs.Patch.Non+ Darcs.Patch.Patchy+ Darcs.Patch.Permutations+ Darcs.Patch.Prim+ Darcs.Patch.Properties+ Darcs.Patch.Read+ Darcs.Patch.ReadMonads+ Darcs.Patch.Real+ Darcs.Patch.Set+ Darcs.Patch.Show+ Darcs.Patch.TouchesFiles+ Darcs.Patch.Viewing+ Darcs.Population+ Darcs.PopulationData+ Darcs.PrintPatch+ Darcs.ProgressPatches+ Darcs.RemoteApply+ Darcs.RepoPath+ Darcs.Repository+ Darcs.Repository.ApplyPatches+ Darcs.Repository.Cache+ Darcs.Repository.Checkpoint+ Darcs.Repository.DarcsRepo+ Darcs.Repository.Format+ Darcs.Repository.HashedIO+ Darcs.Repository.HashedRepo+ Darcs.Repository.Internal+ Darcs.Repository.InternalTypes+ Darcs.Repository.Motd+ Darcs.Repository.Prefs+ Darcs.Repository.Pristine+ Darcs.Repository.Repair+ Darcs.Resolution+ Darcs.RunCommand+ Darcs.Sealed+ Darcs.SelectChanges+ Darcs.Show+ Darcs.SignalHandler+ Darcs.SlurpDirectory+ Darcs.SlurpDirectory.Internal+ Darcs.Test+ Darcs.TheCommands+ Darcs.URL+ Darcs.Utils+ DateMatcher+ English+ Exec+ ByteStringUtils+ HTTP+ IsoDate+ Lcs+ OldDate+ Printer+ Progress+ RegChars+ SHA1+ Ssh+ URL+ UTF8+ Workaround++ other-modules: ThisVersion++ c-sources: src/atomic_create.c+ src/fpstring.c+ src/c_compat.c+ src/maybe_relink.c+ src/umask.c+ src/Crypt/sha2.c+ cc-options: -D_REENTRANT++ if os(windows)+ hs-source-dirs: src/win32+ include-dirs: src/win32+ other-modules: CtrlC+ System.Posix+ System.Posix.Files+ System.Posix.IO+ cpp-options: -DWIN32++ if os(solaris)+ cc-options: -DHAVE_SIGINFO_H++ build-depends: base < 4,+ regex-compat >= 0.71 && < 0.94,+ mtl >= 1.0 && < 1.2,+ parsec >= 2.0 && < 3.1,+ html == 1.0.*,+ filepath == 1.1.*,+ haskeline >= 0.6.1 && < 0.7,+ hashed-storage >= 0.3.3.1 && < 0.4++ if !os(windows)+ build-depends: unix >= 1.0 && < 2.4++ if flag(base3)+ build-depends: base >= 3,+ old-time == 1.0.*,+ directory == 1.0.*,+ process == 1.0.*,+ containers >= 0.1 && < 0.3,+ array >= 0.1 && < 0.3,+ random == 1.0.*+ else+ build-depends: base < 3++ -- We need optimizations, regardless of what Hackage says+ ghc-options: -Wall -O2 -funbox-strict-fields+ ghc-prof-options: -prof -auto-all++ if flag(curl)+ extra-libraries: curl+ includes: curl/curl.h+ cpp-options: -DHAVE_CURL+ c-sources: src/hscurl.c+ cc-options: -DHAVE_CURL++ if flag(curl-pipelining)+ -- curl 7.19.1 has bug-free pipelining+ if !os(windows)+ pkgconfig-depends: libcurl >= 7.19.1+ cpp-options: -DCURL_PIPELINING -DCURL_PIPELINING_DEFAULT++ if flag(http)+ build-depends: network == 2.2.*,+ HTTP >= 3000.0 && < 3001.2+ cpp-options: -DHAVE_HTTP+ x-have-http:++ if !flag(curl) && !flag(http)+ buildable: False++ if flag(mmap) && !os(windows)+ build-depends: mmap >= 0.2+ cpp-options: -DHAVE_MMAP++ if flag(bytestring)+ build-depends: bytestring >= 0.9.0 && < 0.10+ cpp-options: -DHAVE_BYTESTRING++ if flag(zlib)+ build-depends: zlib >= 0.5.1.0 && < 0.6.0.0+ cpp-options: -DHAVE_HASKELL_ZLIB+ else+ extra-libraries: z++ if flag(utf8-string)+ build-depends: utf8-string == 0.3.*+ cpp-options: -DHAVE_UTF8STRING++ -- The terminfo package cannot be built on Windows.+ if flag(terminfo) && !os(windows)+ build-depends: terminfo == 0.3.*+ cpp-options: -DHAVE_TERMINFO++ if flag(color)+ x-use-color:++ extensions:+ CPP,+ ForeignFunctionInterface,+ BangPatterns,+ PatternGuards,+ MagicHash,+ UndecidableInstances,+ DeriveDataTypeable,+ GADTs,+ TypeOperators,+ ExistentialQuantification,+ FlexibleContexts,+ FlexibleInstances,+ ScopedTypeVariables,+ KindSignatures,+ TypeSynonymInstances,+ Rank2Types,+ RankNTypes,+ GeneralizedNewtypeDeriving,+ MultiParamTypeClasses++-- ----------------------------------------------------------------------+-- darcs itself+-- ----------------------------------------------------------------------++Executable darcs+ main-is: darcs.hs+ hs-source-dirs: src+ include-dirs: src+ c-sources: src/atomic_create.c+ src/fpstring.c+ src/c_compat.c+ src/maybe_relink.c+ src/umask.c+ src/Crypt/sha2.c++ -- We need optimizations, regardless of what Hackage says+ ghc-options: -Wall -O2 -funbox-strict-fields -threaded+ ghc-prof-options: -prof -auto-all++ if !flag(zlib)+ extra-libraries: z++ cc-options: -D_REENTRANT++ if os(windows)+ hs-source-dirs: src/win32+ include-dirs: src/win32+ other-modules: CtrlC+ System.Posix+ System.Posix.Files+ System.Posix.IO+ cpp-options: -DWIN32+ c-sources: src/win32/send_email.c++ if os(solaris)+ cc-options: -DHAVE_SIGINFO_H++ build-depends: base < 4,+ regex-compat >= 0.71 && < 0.94,+ mtl >= 1.0 && < 1.2,+ parsec >= 2.0 && < 3.1,+ html == 1.0.*,+ filepath == 1.1.*,+ haskeline >= 0.6.1 && < 0.7,+ hashed-storage >= 0.3.3.1 && < 0.4++ if !os(windows)+ build-depends: unix >= 1.0 && < 2.4++ if flag(base3)+ build-depends: base >= 3,+ old-time == 1.0.*,+ directory == 1.0.*,+ process == 1.0.*,+ containers >= 0.1 && < 0.3,+ array >= 0.1 && < 0.3,+ random == 1.0.*+ else+ build-depends: base < 3++ if flag(curl)+ extra-libraries: curl+ includes: curl/curl.h+ cpp-options: -DHAVE_CURL+ c-sources: src/hscurl.c+ cc-options: -DHAVE_CURL++ if flag(curl-pipelining)+ -- curl 7.19.1 has bug-free pipelining+ if !os(windows)+ pkgconfig-depends: libcurl >= 7.19.1+ cpp-options: -DCURL_PIPELINING -DCURL_PIPELINING_DEFAULT++ if flag(http)+ build-depends: network == 2.2.*,+ HTTP >= 3000.0 && < 3001.2+ cpp-options: -DHAVE_HTTP+ x-have-http:++ if !flag(curl) && !flag(http)+ buildable: False++ if flag(mmap) && !os(windows)+ build-depends: mmap >= 0.2+ cpp-options: -DHAVE_MMAP++ if flag(bytestring)+ build-depends: bytestring >= 0.9.0 && < 0.10+ cpp-options: -DHAVE_BYTESTRING++ if flag(zlib)+ build-depends: zlib >= 0.5.1.0 && < 0.6.0.0+ cpp-options: -DHAVE_HASKELL_ZLIB+ else+ extra-libraries: z++ if flag(utf8-string)+ build-depends: utf8-string == 0.3.*+ cpp-options: -DHAVE_UTF8STRING++ -- The terminfo package cannot be built on Windows.+ if flag(terminfo) && !os(windows)+ build-depends: terminfo == 0.3.*+ cpp-options: -DHAVE_TERMINFO++ if flag(color)+ x-use-color:++ extensions:+ CPP,+ ForeignFunctionInterface,+ BangPatterns,+ PatternGuards,+ MagicHash,+ UndecidableInstances,+ DeriveDataTypeable,+ GADTs,+ TypeOperators,+ ExistentialQuantification,+ FlexibleContexts,+ FlexibleInstances,+ ScopedTypeVariables,+ KindSignatures,+ TypeSynonymInstances,+ Rank2Types,+ RankNTypes,+ GeneralizedNewtypeDeriving,+ MultiParamTypeClasses++-- ----------------------------------------------------------------------+-- unit test driver+-- ----------------------------------------------------------------------++Executable unit+ main-is: unit.lhs+ hs-source-dirs: src+ include-dirs: src+ c-sources: src/atomic_create.c+ src/fpstring.c+ src/c_compat.c+ src/maybe_relink.c+ src/umask.c+ src/Crypt/sha2.c++ -- We need optimizations, regardless of what Hackage says+ ghc-options: -Wall -O2 -funbox-strict-fields -threaded+ ghc-prof-options: -prof -auto-all++ if !flag(test)+ buildable: False+ else+ buildable: True+ build-depends: base < 4,+ regex-compat >= 0.71 && < 0.94,+ mtl >= 1.0 && < 1.2,+ parsec >= 2.0 && < 3.1,+ html == 1.0.*,+ filepath == 1.1.*,+ QuickCheck >= 2.1.0.0,+ HUnit >= 1.0,+ test-framework >= 0.2.2,+ test-framework-hunit >= 0.2.2,+ test-framework-quickcheck2 >= 0.2.2+++ if !flag(zlib)+ extra-libraries: z++ cc-options: -D_REENTRANT++ if os(windows)+ hs-source-dirs: src/win32+ include-dirs: src/win32+ other-modules: CtrlC+ System.Posix+ System.Posix.Files+ System.Posix.IO+ cpp-options: -DWIN32+ c-sources: src/win32/send_email.c++ if os(solaris)+ cc-options: -DHAVE_SIGINFO_H++ if !os(windows)+ build-depends: unix >= 1.0 && < 2.4++ if flag(base3)+ build-depends: base >= 3,+ old-time == 1.0.*,+ directory == 1.0.*,+ process == 1.0.*,+ containers >= 0.1 && < 0.3,+ array >= 0.1 && < 0.3,+ random == 1.0.*+ else+ build-depends: base < 3++ if flag(mmap) && !os(windows)+ build-depends: mmap >= 0.2+ cpp-options: -DHAVE_MMAP++ if flag(bytestring)+ build-depends: bytestring >= 0.9.0 && < 0.10+ cpp-options: -DHAVE_BYTESTRING++ if flag(zlib)+ build-depends: zlib >= 0.5.1.0 && < 0.6.0.0+ cpp-options: -DHAVE_HASKELL_ZLIB+ else+ extra-libraries: z++ -- The terminfo package cannot be built on Windows.+ if flag(terminfo) && !os(windows)+ build-depends: terminfo == 0.3.*+ cpp-options: -DHAVE_TERMINFO++ if flag(color)+ x-use-color:++ extensions:+ CPP,+ ForeignFunctionInterface,+ BangPatterns,+ PatternGuards,+ MagicHash,+ UndecidableInstances,+ DeriveDataTypeable,+ GADTs,+ TypeOperators,+ ExistentialQuantification,+ FlexibleContexts,+ FlexibleInstances,+ ScopedTypeVariables,+ KindSignatures,+ TypeSynonymInstances,+ Rank2Types,+ RankNTypes,+ GeneralizedNewtypeDeriving,+ MultiParamTypeClasses+ OverlappingInstances
@@ -0,0 +1,1 @@+Just "\nContext:\n\n[TAG 2.2.98.1\nPetr Rockai <me@mornfall.net>**20090623155657\n Ignore-this: b388b0221526b2609dec75bb733bfc4c\n] \n"
@@ -0,0 +1,1 @@+Just 0
@@ -0,0 +1,575 @@+{-# LANGUAGE BangPatterns, ForeignFunctionInterface, CPP, ScopedTypeVariables #-}++-----------------------------------------------------------------------------+-- |+-- Module : ByteStringUtils+-- Copyright : (c) The University of Glasgow 2001,+-- David Roundy 2003-2005+-- License : GPL (I'm happy to also license this file BSD style but don't+-- want to bother distributing two license files with darcs.+--+-- Maintainer : droundy@abridgegame.org+-- Stability : experimental+-- Portability : portable+--+-- GZIp and MMap IO for ByteStrings, and miscellaneous functions for Data.ByteString+--++module ByteStringUtils (++ unsafeWithInternals,+ unpackPSfromUTF8,++ -- IO with mmap or gzip+ gzReadFilePS,+ mmapFilePS,+ gzWriteFilePS,+ gzWriteFilePSs,++ -- gzip handling+ isGZFile,+#ifdef HAVE_HASKELL_ZLIB+ gzDecompress,+#endif++ -- list utilities+ ifHeadThenTail,+ dropSpace,+ breakSpace,+ linesPS,+ unlinesPS,+ hashPS,+ breakFirstPS,+ breakLastPS,+ substrPS,+ readIntPS,+ is_funky,+ fromHex2PS,+ fromPS2Hex,+ betweenLinesPS,+ break_after_nth_newline,+ break_before_nth_newline,+ intercalate+ ) where++import Prelude hiding ( catch )+import qualified Data.ByteString as B+import qualified Data.ByteString.Char8 as BC+import qualified Data.ByteString.Internal as BI+import Data.ByteString (intercalate, uncons)+import Data.ByteString.Internal (fromForeignPtr)++#if defined (HAVE_MMAP) || ! defined (HAVE_HASKELL_ZLIB)+import Control.Exception ( catch )+#endif+import System.IO+import System.IO.Unsafe ( unsafePerformIO )++import Foreign.Storable ( peekElemOff, peek )+import Foreign.Marshal.Alloc ( free )+import Foreign.Marshal.Array ( mallocArray, peekArray, advancePtr )+import Foreign.C.Types ( CInt )++import Data.Bits ( rotateL )+import Data.Char ( chr, ord, isSpace )+import Data.Word ( Word8 )+import Data.Int ( Int32 )+import Control.Monad ( when )++#ifndef HAVE_HASKELL_ZLIB+import Foreign.Ptr ( nullPtr )+import Foreign.ForeignPtr ( ForeignPtr )+#endif+import Foreign.Ptr ( plusPtr, Ptr )+import Foreign.ForeignPtr ( withForeignPtr )++#ifdef DEBUG_PS+import Foreign.ForeignPtr ( addForeignPtrFinalizer )+import Foreign.Ptr ( FunPtr )+#endif++#if HAVE_HASKELL_ZLIB+import qualified Data.ByteString.Lazy as BL+import qualified Codec.Compression.GZip as GZ+import qualified Codec.Compression.Zlib.Internal as ZI+import Darcs.Global ( addCRCWarning )+#else+import Foreign.C.String ( CString, withCString )+#endif++#ifdef HAVE_MMAP+import System.IO.MMap( mmapFileByteString )+import System.Mem( performGC )+import System.Posix.Files( fileSize, getSymbolicLinkStatus )+#endif++-- -----------------------------------------------------------------------------+-- obsolete debugging code++# ifndef HAVE_HASKELL_ZLIB+debugForeignPtr :: ForeignPtr a -> String -> IO ()+#ifdef DEBUG_PS+foreign import ccall unsafe "static fpstring.h debug_alloc" debug_alloc+ :: Ptr a -> CString -> IO ()+foreign import ccall unsafe "static fpstring.h & debug_free" debug_free+ :: FunPtr (Ptr a -> IO ())+debugForeignPtr fp n =+ withCString n $ \cname-> withForeignPtr fp $ \p->+ do debug_alloc p cname+ addForeignPtrFinalizer debug_free fp+#else+debugForeignPtr _ _ = return ()+#endif+#endif++-- -----------------------------------------------------------------------------+-- unsafeWithInternals++-- | Do something with the internals of a PackedString. Beware of+-- altering the contents!+unsafeWithInternals :: B.ByteString -> (Ptr Word8 -> Int -> IO a) -> IO a+unsafeWithInternals ps f+ = case BI.toForeignPtr ps of+ (fp,s,l) -> withForeignPtr fp $ \p -> f (p `plusPtr` s) l++-- | readIntPS skips any whitespace at the beginning of its argument, and+-- reads an Int from the beginning of the PackedString. If there is no+-- integer at the beginning of the string, it returns Nothing, otherwise it+-- just returns the int read, along with a B.ByteString containing the+-- remainder of its input.++readIntPS :: B.ByteString -> Maybe (Int, B.ByteString)+readIntPS = BC.readInt . BC.dropWhile isSpace++-- -----------------------------------------------------------------------------+-- Destructor functions (taking PackedStrings apart)++unpackPSfromUTF8 :: B.ByteString -> String+unpackPSfromUTF8 ps =+ case BI.toForeignPtr ps of+ (_,_, 0) -> ""+ (x,s,l) ->+ unsafePerformIO $ withForeignPtr x $ \p->+ do outbuf <- mallocArray l+ lout <- fromIntegral `fmap`+ utf8_to_ints outbuf (p `plusPtr` s) (fromIntegral l)+ when (lout < 0) $ error "Bad UTF8!"+ str <- (map (chr . fromIntegral)) `fmap` peekArray lout outbuf+ free outbuf+ return str++foreign import ccall unsafe "static fpstring.h utf8_to_ints" utf8_to_ints+ :: Ptr Int -> Ptr Word8 -> CInt -> IO CInt++-- -----------------------------------------------------------------------------+-- List-mimicking functions for PackedStrings++{-# INLINE ifHeadThenTail #-}+ifHeadThenTail :: Word8 -> B.ByteString -> Maybe B.ByteString+ifHeadThenTail c s = case uncons s of+ Just (w, t) | w == c -> Just t+ _ -> Nothing++------------------------------------------------------------------------+-- A reimplementation of Data.ByteString.Char8.dropSpace, but+-- specialised to darcs' need for a 4 way isspace.+--+-- TODO: if it is safe to use the expanded definition of isSpaceWord8+-- provided by Data.ByteString.Char8, then all this can go.++-- A locale-independent isspace(3) so patches are interpreted the same everywhere.+-- ((c) == ' ' || (c) == '\t' || (c) == '\n' || (c) == '\r')+isSpaceWord8 :: Word8 -> Bool+isSpaceWord8 w =+ w == 0x20 || -- ' '+ w == 0x09 || -- '\t'+ w == 0x0A || -- '\n'+ w == 0x0D -- '\r'+{-# INLINE isSpaceWord8 #-}++firstnonspace :: Ptr Word8 -> Int -> Int -> IO Int+firstnonspace !ptr !n !m+ | n >= m = return n+ | otherwise = do w <- peekElemOff ptr n+ if isSpaceWord8 w then firstnonspace ptr (n+1) m else return n++firstspace :: Ptr Word8 -> Int -> Int -> IO Int+firstspace !ptr !n !m+ | n >= m = return n+ | otherwise = do w <- peekElemOff ptr n+ if (not . isSpaceWord8) w then firstspace ptr (n+1) m else return n++-- | 'dropSpace' efficiently returns the 'ByteString' argument with+-- white space Chars removed from the front. It is more efficient than+-- calling dropWhile for removing whitespace. I.e.+-- +-- > dropWhile isSpace == dropSpace+--+dropSpace :: B.ByteString -> B.ByteString+dropSpace (BI.PS x s l) = BI.inlinePerformIO $ withForeignPtr x $ \p -> do+ i <- firstnonspace (p `plusPtr` s) 0 l+ return $! if i == l then B.empty else BI.PS x (s+i) (l-i)+{-# INLINE dropSpace #-}++-- | 'breakSpace' returns the pair of ByteStrings when the argument is+-- broken at the first whitespace byte. I.e.+-- +-- > break isSpace == breakSpace+--+breakSpace :: B.ByteString -> (B.ByteString,B.ByteString)+breakSpace (BI.PS x s l) = BI.inlinePerformIO $ withForeignPtr x $ \p -> do+ i <- firstspace (p `plusPtr` s) 0 l+ return $! case () of {_+ | i == 0 -> (B.empty, BI.PS x s l)+ | i == l -> (BI.PS x s l, B.empty)+ | otherwise -> (BI.PS x s i, BI.PS x (s+i) (l-i))+ }+{-# INLINE breakSpace #-}++------------------------------------------------------------------------++{-# INLINE is_funky #-}+is_funky :: B.ByteString -> Bool+is_funky ps = case BI.toForeignPtr ps of+ (x,s,l) ->+ unsafePerformIO $ withForeignPtr x $ \p->+ (/=0) `fmap` has_funky_char (p `plusPtr` s) (fromIntegral l)++foreign import ccall unsafe "fpstring.h has_funky_char" has_funky_char+ :: Ptr Word8 -> CInt -> IO CInt++------------------------------------------------------------------------++-- ByteString rewrites break (=='x') to breakByte 'x'+-- break ((==) x) = breakChar x+-- break (==x) = breakChar x+--++{-+{-# INLINE breakOnPS #-}+breakOnPS :: Char -> B.ByteString -> (B.ByteString, B.ByteString)+breakOnPS c p = case BC.elemIndex c p of+ Nothing -> (p, BC.empty)+ Just n -> (B.take n p, B.drop n p)+-}++{-# INLINE hashPS #-}+hashPS :: B.ByteString -> Int32+hashPS ps =+ case BI.toForeignPtr ps of+ (x,s,l) ->+ unsafePerformIO $ withForeignPtr x $ \p->+ do hash (p `plusPtr` s) l++hash :: Ptr Word8 -> Int -> IO Int32+hash ptr len = f (0 :: Int32) ptr len+ where f h _ 0 = return h+ f h p n = do x <- peek p+ let !h' = (fromIntegral x) + (rotateL h 8)+ f h' (p `advancePtr` 1) (n-1)++{-# INLINE substrPS #-}+substrPS :: B.ByteString -> B.ByteString -> Maybe Int+substrPS tok str+ | B.null tok = Just 0+ | B.length tok > B.length str = Nothing+ | otherwise = do n <- BC.elemIndex (BC.head tok) str+ let ttok = B.tail tok+ reststr = B.drop (n+1) str+ if ttok == B.take (B.length ttok) reststr+ then Just n+ else ((n+1)+) `fmap` substrPS tok reststr++------------------------------------------------------------------------++-- TODO: replace breakFirstPS and breakLastPS with definitions based on+-- ByteString's break/breakEnd+{-# INLINE breakFirstPS #-}+breakFirstPS :: Char -> B.ByteString -> Maybe (B.ByteString,B.ByteString)+breakFirstPS c p = case BC.elemIndex c p of+ Nothing -> Nothing+ Just n -> Just (B.take n p, B.drop (n+1) p)++{-# INLINE breakLastPS #-}+breakLastPS :: Char -> B.ByteString -> Maybe (B.ByteString,B.ByteString)+breakLastPS c p = case BC.elemIndexEnd c p of+ Nothing -> Nothing+ Just n -> Just (B.take n p, B.drop (n+1) p)++-- TODO: rename+{-# INLINE linesPS #-}+linesPS :: B.ByteString -> [B.ByteString]+linesPS ps+ | B.null ps = [B.empty]+ | otherwise = BC.split '\n' ps++{- QuickCheck property:++import Test.QuickCheck+import qualified Data.ByteString.Char8 as BC+import Data.Char+instance Arbitrary BC.ByteString where+ arbitrary = fmap BC.pack arbitrary+instance Arbitrary Char where+ arbitrary = chr `fmap` choose (32,127)+deepCheck = check (defaultConfig { configMaxTest = 10000})+testLines = deepCheck (\x -> (linesPS x == linesPSOld x))+linesPSOld ps = case BC.elemIndex '\n' ps of+ Nothing -> [ps]+ Just n -> B.take n ps : linesPS (B.drop (n+1) ps) -}++{-| This function acts exactly like the "Prelude" unlines function, or like+"Data.ByteString.Char8" 'unlines', but with one important difference: it will+produce a string which may not end with a newline! That is:++> unlinesPS ["foo", "bar"]++evaluates to \"foo\\nbar\", not \"foo\\nbar\\n\"! This point should hold true for+'linesPS' as well.++TODO: rename this function. -}+unlinesPS :: [B.ByteString] -> B.ByteString+unlinesPS [] = BC.empty+unlinesPS x = BC.init $ BC.unlines x+{-# INLINE unlinesPS #-}+{- QuickCheck property:++testUnlines = deepCheck (\x -> (unlinesPS x == unlinesPSOld x))+unlinesPSOld ss = BC.concat $ intersperse_newlines ss+ where intersperse_newlines (a:b:s) = a : newline : intersperse_newlines (b:s)+ intersperse_newlines s = s+ newline = BC.pack "\n" -}++-- -----------------------------------------------------------------------------+-- gzReadFilePS++#ifdef HAVE_HASKELL_ZLIB++gzDecompress :: forall m . (Functor m, Monad m) => m () -> Maybe Int -> BL.ByteString -> m [B.ByteString]+gzDecompress reportBadCRC mbufsize =+ -- This is what the code would be without the bad CRC recovery logic:+ -- return . BL.toChunks . GZ.decompressWith decompressParams+ toListWarn . ZI.decompressWithErrors ZI.GZip decompressParams+ where+ decompressParams = case mbufsize of+ Just bufsize -> GZ.defaultDecompressParams { GZ.decompressBufferSize = bufsize }+ Nothing -> GZ.defaultDecompressParams++ toListWarn :: ZI.DecompressStream -> m [B.ByteString]+ toListWarn ZI.StreamEnd = return []+ toListWarn (ZI.StreamChunk x xs) = fmap (x:) (toListWarn xs)+ -- For a while a bug in darcs caused gzip files with good data but bad CRCs to be+ -- produced. Trap bad CRC messages, run the specified action to report that it happened,+ -- but continue on the assumption that the data is valid.+ toListWarn (ZI.StreamError ZI.DataError "incorrect data check") = reportBadCRC >> return []+ toListWarn (ZI.StreamError _ msg) = fail msg++#else++foreign import ccall unsafe "static zlib.h gzopen" c_gzopen+ :: CString -> CString -> IO (Ptr ())+foreign import ccall unsafe "static zlib.h gzclose" c_gzclose+ :: Ptr () -> IO ()+foreign import ccall unsafe "static zlib.h gzread" c_gzread+ :: Ptr () -> Ptr Word8 -> CInt -> IO CInt+foreign import ccall unsafe "static zlib.h gzwrite" c_gzwrite+ :: Ptr () -> Ptr Word8 -> CInt -> IO CInt++#endif++isGZFile :: FilePath -> IO (Maybe Int)+isGZFile f = do+ h <- openBinaryFile f ReadMode+ header <- B.hGet h 2+ if header /= BC.pack "\31\139"+ then do hClose h+ return Nothing+ else do hSeek h SeekFromEnd (-4)+ len <- hGetLittleEndInt h+ hClose h+ return (Just len)++-- | Read an entire file, which may or may not be gzip compressed, directly+-- into a 'B.ByteString'.+gzReadFilePS :: FilePath -> IO B.ByteString+gzReadFilePS f = do+ mlen <- isGZFile f+ case mlen of+ Nothing -> mmapFilePS f+ Just len ->+#ifdef HAVE_HASKELL_ZLIB+ -- Passing the length to gzDecompress means that it produces produces one chunk,+ -- which in turn means that B.concat won't need to copy data.+ -- If the length is wrong this will just affect efficiency, not correctness+ fmap B.concat . (gzDecompress (addCRCWarning f) (Just len) =<<) $+#ifdef HAVE_OLD_BYTESTRING+ -- bytestring < 0.9.1 had a bug where it did not know to close handles upon EOF+ -- performance would be better with a newer bytestring and lazy+ -- readFile below -- ratify readFile: comment+ fmap (BL.fromChunks . (:[])) $+ B.readFile f -- ratify readFile: immediately consumed+#else+ BL.readFile f -- ratify readFile: immediately consumed by the conversion to a strict bytestring+#endif+#else+ withCString f $ \fstr-> withCString "rb" $ \rb-> do+ gzf <- c_gzopen fstr rb+ when (gzf == nullPtr) $ fail $ "problem opening file "++f+ fp <- BI.mallocByteString len+ debugForeignPtr fp $ "gzReadFilePS "++f+ lread <- withForeignPtr fp $ \p ->+ c_gzread gzf p (fromIntegral len)+ c_gzclose gzf+ when (fromIntegral lread /= len) $+ fail $ "problem gzreading file "++f+ return $ fromForeignPtr fp 0 len+#endif++hGetLittleEndInt :: Handle -> IO Int+hGetLittleEndInt h = do+ b1 <- ord `fmap` hGetChar h+ b2 <- ord `fmap` hGetChar h+ b3 <- ord `fmap` hGetChar h+ b4 <- ord `fmap` hGetChar h+ return $ b1 + 256*b2 + 65536*b3 + 16777216*b4++gzWriteFilePS :: FilePath -> B.ByteString -> IO ()+gzWriteFilePS f ps = gzWriteFilePSs f [ps]++gzWriteFilePSs :: FilePath -> [B.ByteString] -> IO ()+gzWriteFilePSs f pss =+#ifdef HAVE_HASKELL_ZLIB+ BL.writeFile f $ GZ.compress $ BL.fromChunks pss+#else+ withCString f $ \fstr -> withCString "wb" $ \wb -> do+ gzf <- c_gzopen fstr wb+ when (gzf == nullPtr) $ fail $ "problem gzopening file for write: "++f+ mapM_ (gzWriteToGzf gzf) pss `catch`+ \_ -> fail $ "problem gzwriting file: "++f+ c_gzclose gzf++gzWriteToGzf :: Ptr () -> B.ByteString -> IO ()+gzWriteToGzf gzf ps = case BI.toForeignPtr ps of+ (_,_,0) -> return () -- avoid calling gzwrite with 0 length this would+ -- trouble on some versions of zlib, and is always+ -- unnecessary.+ (x,s,l) -> do+ lw <- withForeignPtr x $ \p -> c_gzwrite gzf (p `plusPtr` s)+ (fromIntegral l)+ when (fromIntegral lw /= l) $ fail $ "problem in gzWriteToGzf"+#endif++-- -----------------------------------------------------------------------------+-- mmapFilePS++-- | Like readFilePS, this reads an entire file directly into a+-- 'B.ByteString', but it is even more efficient. It involves directly+-- mapping the file to memory. This has the advantage that the contents of+-- the file never need to be copied. Also, under memory pressure the page+-- may simply be discarded, wile in the case of readFilePS it would need to+-- be written to swap. If you read many small files, mmapFilePS will be+-- less memory-efficient than readFilePS, since each mmapFilePS takes up a+-- separate page of memory. Also, you can run into bus errors if the file+-- is modified. NOTE: as with 'readFilePS', the string representation in+-- the file is assumed to be ISO-8859-1.++mmapFilePS :: FilePath -> IO B.ByteString+#ifdef HAVE_MMAP+mmapFilePS f = do+ x <- mmapFileByteString f Nothing+ `catch` (\_ -> do+ size <- fileSize `fmap` getSymbolicLinkStatus f+ if size == 0+ then return B.empty+ else performGC >> mmapFileByteString f Nothing)+ return x+#else+mmapFilePS = B.readFile+#endif++-- -------------------------------------------------------------------------+-- fromPS2Hex++foreign import ccall unsafe "static fpstring.h conv_to_hex" conv_to_hex+ :: Ptr Word8 -> Ptr Word8 -> CInt -> IO ()++fromPS2Hex :: B.ByteString -> B.ByteString+fromPS2Hex ps = case BI.toForeignPtr ps of+ (x,s,l) ->+ BI.unsafeCreate (2*l) $ \p -> withForeignPtr x $ \f ->+ conv_to_hex p (f `plusPtr` s) $ fromIntegral l++-- -------------------------------------------------------------------------+-- fromHex2PS++foreign import ccall unsafe "static fpstring.h conv_from_hex" conv_from_hex+ :: Ptr Word8 -> Ptr Word8 -> CInt -> IO ()++fromHex2PS :: B.ByteString -> B.ByteString+fromHex2PS ps = case BI.toForeignPtr ps of+ (x,s,l) ->+ BI.unsafeCreate (l `div` 2) $ \p -> withForeignPtr x $ \f ->+ conv_from_hex p (f `plusPtr` s) (fromIntegral $ l `div` 2)++-- -------------------------------------------------------------------------+-- betweenLinesPS++-- | betweenLinesPS returns the B.ByteString between the two lines given,+-- or Nothing if they do not appear.++betweenLinesPS :: B.ByteString -> B.ByteString -> B.ByteString+ -> Maybe (B.ByteString)+betweenLinesPS start end ps+ = case break (start ==) (linesPS ps) of+ (_, _:rest@(bs1:_)) ->+ case BI.toForeignPtr bs1 of+ (ps1,s1,_) ->+ case break (end ==) rest of+ (_, bs2:_) -> case BI.toForeignPtr bs2 of (_,s2,_) -> Just $ fromForeignPtr ps1 s1 (s2 - s1)+ _ -> Nothing+ _ -> Nothing++-- -------------------------------------------------------------------------+-- break_after_nth_newline++break_after_nth_newline :: Int -> B.ByteString+ -> Maybe (B.ByteString, B.ByteString)+break_after_nth_newline 0 the_ps | B.null the_ps = Just (B.empty, B.empty)+break_after_nth_newline n the_ps =+ case BI.toForeignPtr the_ps of+ (fp,the_s,l) ->+ unsafePerformIO $ withForeignPtr fp $ \p ->+ do let findit 0 s | s == end = return $ Just (the_ps, B.empty)+ findit _ s | s == end = return Nothing+ findit 0 s = let left_l = s - the_s+ in return $ Just (fromForeignPtr fp the_s left_l,+ fromForeignPtr fp s (l - left_l))+ findit i s = do w <- peekElemOff p s+ if w == nl then findit (i-1) (s+1)+ else findit i (s+1)+ nl = BI.c2w '\n'+ end = the_s + l+ findit n the_s++-- -------------------------------------------------------------------------+-- break_before_nth_newline++break_before_nth_newline :: Int -> B.ByteString -> (B.ByteString, B.ByteString)+break_before_nth_newline 0 the_ps+ | B.null the_ps = (B.empty, B.empty)+break_before_nth_newline n the_ps =+ case BI.toForeignPtr the_ps of+ (fp,the_s,l) ->+ unsafePerformIO $ withForeignPtr fp $ \p ->+ do let findit _ s | s == end = return (the_ps, B.empty)+ findit i s = do w <- peekElemOff p s+ if w == nl+ then if i == 0+ then let left_l = s - the_s+ in return (fromForeignPtr fp the_s left_l,+ fromForeignPtr fp s (l - left_l))+ else findit (i-1) (s+1)+ else findit i (s+1)+ nl = BI.c2w '\n'+ end = the_s + l+ findit n the_s
@@ -0,0 +1,122 @@+-- Copyright (C) 2005 Benedikt Schmidt+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.++-- |A parser for commandlines, returns an arg list and expands+-- format strings given in a translation table. Additionally+-- the commandline can end with "%<" specifying that the command+-- expects input on stdin.+--+-- Some tests for the parser.+--+-- > formatTable = [('s',"<insert subject here>"),+-- > ('a',"<insert author here>")]+-- >+-- > testParser :: (Show a, Eq a) => Parser a -> String -> a -> a+-- > testParser p s ok = case parse p "" s of+-- > Left e -> error $ "Parser failed with: " ++ (show e)+-- > Right res -> if res == ok+-- > then res+-- > else error $ "Parser failed: got "+-- > ++ (show res) ++ ", expected "+-- > ++ (show ok)+-- >+-- > testCases = [("a b",(["a","b"], False)),+-- > ("a b %<",(["a","b"], True)),+-- > ("a b %< ",(["a","b"], True)),+-- > ("\"arg0 contains spaces \\\"quotes\\\"\" b",+-- > (["arg0 contains spaces \"quotes\"","b"],False)),+-- > ("a %s %<",(["a","<insert subject here>"], True))]+-- >+-- > runTests = map (uncurry $ testParser (commandline formatTable)) testCases+module CommandLine ( parseCmd, addUrlencoded ) where+import Text.ParserCombinators.Parsec+import Data.Char ( ord, intToDigit, toUpper )+import Data.List ( find )++-- | assoc list mapping characters to strings+-- eg (c,s) means that %c is replaced by s+type FTable = [(Char,String)]+commandline :: FTable -> Parser ([String], Bool)+commandline ftable = consumeAll (do l <- sepEndBy1 (arg ftable) + (try separator)+ redir <- formatRedir+ spaces+ return (l,redir))++escape:: Parser String+arg, format, quotedArg, unquotedArg, quoteContent :: FTable -> Parser String+arg ftable = (quotedArg ftable <|> unquotedArg ftable)++unquotedArg ftable = do (try $ format ftable)+ <|> (many1 $ noneOf [' ', '\t', '"', '%'])++quotedArg ftable = between (char '"') (char '"') $ quoteContent ftable++quoteContent ftable = do s1 <- escape+ <|> (try $ format ftable)+ <|> (many1 (noneOf ['"', '\\', '%']))+ s2 <- quoteContent ftable+ return $ s1 ++ s2+ <|> return ""++formatRedir :: Parser Bool+formatRedir = do string "%<"+ return True+ <|> return False++format ftable = do char '%'+ c <- oneOf (map fst ftable)+ return $ expandFormat ftable c++escape = do char '\\'+ c <- anyChar+ return [c]++consumeAll :: Parser a -> Parser a+consumeAll p = do r <- p+ eof+ return r+ +separator :: Parser ()+separator = do skipMany1 space++expandFormat :: FTable -> Char -> String+expandFormat ftable c = case find ((==c) . fst) ftable of+ Just (_,s) -> s+ Nothing -> error "impossible"++-- | parse a commandline returning a list of strings +-- (intended to be used as argv) and a bool value which+-- specifies if the command expects input on stdin+-- format specifiers with a mapping in ftable are accepted+-- and replaced by the given strings. E.g. if the ftable is+-- [('s',"Some subject")], then "%s" is replaced by "Some subject"+parseCmd :: FTable -> String -> Either ParseError ([String],Bool)+parseCmd ftable s = parse (commandline ftable) "" s++urlEncode :: String -> String+urlEncode s = concat $ map escapeC s+ where escapeC x = if allowed x then [x] else '%':(intToHex $ ord x)+ intToHex i = map intToDigit [i `div` 16, i `mod` 16] + allowed x = x `elem` ['a' .. 'z'] ++ ['A' .. 'Z'] ++ ['0' .. '9']+ ++ "!'()*-.~"++-- | for every mapping (c,s), add a mapping with uppercase c+-- and the urlencoded string s+addUrlencoded :: FTable -> FTable+addUrlencoded ftable =+ ftable ++(map (\ (c,x) -> (toUpper c, urlEncode x)) ftable)
@@ -0,0 +1,44 @@+{-# LANGUAGE CPP, ForeignFunctionInterface #-}++-- |+-- Module: Data.Digest.SHA256+-- Copyright: Zooko O'Whielacronx+-- License: GPL+--+-- Stability: experimental++-- ByteString-based, zero-copying binding to Crypto++'s sha interface++-- thanks to Don Stewart <dons@galois.com>, Matthew Sackman+-- <matthew@wellquite.org>, Brian O'Sullivan, lispy, Adam Langley++module Crypt.SHA256 ( sha256sum ) where++import Foreign+import Foreign.C.Types+import Numeric (showHex)+import Foreign.C.String ( withCString )+import Data.ByteString.Unsafe (unsafeUseAsCStringLen)+import qualified Data.ByteString as B++sha256sum :: B.ByteString -> String+sha256sum p = unsafePerformIO $+ withCString (take 64 $ repeat 'x') $ \digestCString ->+ unsafeUseAsCStringLen p $ \(ptr,n) ->+ do let digest = castPtr digestCString :: Ptr Word8+ c_sha256 ptr (fromIntegral n) digest+ go digest 0 []+ where -- print it in 0-padded hex format+ go :: Ptr Word8 -> Int -> [String] -> IO String+ go q n acc | seq q n >= 32 = return $ concat (reverse acc)+ | otherwise = do w <- peekElemOff q n+ go q (n+1) (draw w : acc)+ draw w = case showHex w [] of+ [x] -> ['0', x]+ x -> x++-- void sha256sum(const unsigned char *d, size_t n, unsigned char *md);+--+foreign import ccall unsafe "sha2.h sha256" c_sha256+ :: Ptr CChar -> CSize -> Ptr Word8 -> IO ()+
@@ -0,0 +1,950 @@+/*+ * FIPS 180-2 SHA-224/256/384/512 implementation+ * Last update: 02/02/2007+ * Issue date: 04/30/2005+ *+ * Copyright (C) 2005, 2007 Olivier Gay <olivier.gay@a3.epfl.ch>+ * All rights reserved.+ *+ * Redistribution and use in source and binary forms, with or without+ * modification, are permitted provided that the following conditions+ * are met:+ * 1. Redistributions of source code must retain the above copyright+ * notice, this list of conditions and the following disclaimer.+ * 2. Redistributions in binary form must reproduce the above copyright+ * notice, this list of conditions and the following disclaimer in the+ * documentation and/or other materials provided with the distribution.+ * 3. Neither the name of the project nor the names of its contributors+ * may be used to endorse or promote products derived from this software+ * without specific prior written permission.+ *+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE+ * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF+ * SUCH DAMAGE.+ */++#if 0+#define UNROLL_LOOPS /* Enable loops unrolling */+#endif++#include <string.h>++#include "sha2.h"++#define SHFR(x, n) (x >> n)+#define ROTR(x, n) ((x >> n) | (x << ((sizeof(x) << 3) - n)))+#define ROTL(x, n) ((x << n) | (x >> ((sizeof(x) << 3) - n)))+#define CH(x, y, z) ((x & y) ^ (~x & z))+#define MAJ(x, y, z) ((x & y) ^ (x & z) ^ (y & z))++#define SHA256_F1(x) (ROTR(x, 2) ^ ROTR(x, 13) ^ ROTR(x, 22))+#define SHA256_F2(x) (ROTR(x, 6) ^ ROTR(x, 11) ^ ROTR(x, 25))+#define SHA256_F3(x) (ROTR(x, 7) ^ ROTR(x, 18) ^ SHFR(x, 3))+#define SHA256_F4(x) (ROTR(x, 17) ^ ROTR(x, 19) ^ SHFR(x, 10))++#define SHA512_F1(x) (ROTR(x, 28) ^ ROTR(x, 34) ^ ROTR(x, 39))+#define SHA512_F2(x) (ROTR(x, 14) ^ ROTR(x, 18) ^ ROTR(x, 41))+#define SHA512_F3(x) (ROTR(x, 1) ^ ROTR(x, 8) ^ SHFR(x, 7))+#define SHA512_F4(x) (ROTR(x, 19) ^ ROTR(x, 61) ^ SHFR(x, 6))++#define UNPACK32(x, str) \+{ \+ *((str) + 3) = (uint8) ((x) ); \+ *((str) + 2) = (uint8) ((x) >> 8); \+ *((str) + 1) = (uint8) ((x) >> 16); \+ *((str) + 0) = (uint8) ((x) >> 24); \+}++#define PACK32(str, x) \+{ \+ *(x) = ((uint32) *((str) + 3) ) \+ | ((uint32) *((str) + 2) << 8) \+ | ((uint32) *((str) + 1) << 16) \+ | ((uint32) *((str) + 0) << 24); \+}++#define UNPACK64(x, str) \+{ \+ *((str) + 7) = (uint8) ((x) ); \+ *((str) + 6) = (uint8) ((x) >> 8); \+ *((str) + 5) = (uint8) ((x) >> 16); \+ *((str) + 4) = (uint8) ((x) >> 24); \+ *((str) + 3) = (uint8) ((x) >> 32); \+ *((str) + 2) = (uint8) ((x) >> 40); \+ *((str) + 1) = (uint8) ((x) >> 48); \+ *((str) + 0) = (uint8) ((x) >> 56); \+}++#define PACK64(str, x) \+{ \+ *(x) = ((uint64) *((str) + 7) ) \+ | ((uint64) *((str) + 6) << 8) \+ | ((uint64) *((str) + 5) << 16) \+ | ((uint64) *((str) + 4) << 24) \+ | ((uint64) *((str) + 3) << 32) \+ | ((uint64) *((str) + 2) << 40) \+ | ((uint64) *((str) + 1) << 48) \+ | ((uint64) *((str) + 0) << 56); \+}++/* Macros used for loops unrolling */++#define SHA256_SCR(i) \+{ \+ w[i] = SHA256_F4(w[i - 2]) + w[i - 7] \+ + SHA256_F3(w[i - 15]) + w[i - 16]; \+}++#define SHA512_SCR(i) \+{ \+ w[i] = SHA512_F4(w[i - 2]) + w[i - 7] \+ + SHA512_F3(w[i - 15]) + w[i - 16]; \+}++#define SHA256_EXP(a, b, c, d, e, f, g, h, j) \+{ \+ t1 = wv[h] + SHA256_F2(wv[e]) + CH(wv[e], wv[f], wv[g]) \+ + sha256_k[j] + w[j]; \+ t2 = SHA256_F1(wv[a]) + MAJ(wv[a], wv[b], wv[c]); \+ wv[d] += t1; \+ wv[h] = t1 + t2; \+}++#define SHA512_EXP(a, b, c, d, e, f, g ,h, j) \+{ \+ t1 = wv[h] + SHA512_F2(wv[e]) + CH(wv[e], wv[f], wv[g]) \+ + sha512_k[j] + w[j]; \+ t2 = SHA512_F1(wv[a]) + MAJ(wv[a], wv[b], wv[c]); \+ wv[d] += t1; \+ wv[h] = t1 + t2; \+}++uint32 sha224_h0[8] =+ {0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939,+ 0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4};++uint32 sha256_h0[8] =+ {0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,+ 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19};++uint64 sha384_h0[8] =+ {0xcbbb9d5dc1059ed8ULL, 0x629a292a367cd507ULL,+ 0x9159015a3070dd17ULL, 0x152fecd8f70e5939ULL,+ 0x67332667ffc00b31ULL, 0x8eb44a8768581511ULL,+ 0xdb0c2e0d64f98fa7ULL, 0x47b5481dbefa4fa4ULL};++uint64 sha512_h0[8] =+ {0x6a09e667f3bcc908ULL, 0xbb67ae8584caa73bULL,+ 0x3c6ef372fe94f82bULL, 0xa54ff53a5f1d36f1ULL,+ 0x510e527fade682d1ULL, 0x9b05688c2b3e6c1fULL,+ 0x1f83d9abfb41bd6bULL, 0x5be0cd19137e2179ULL};++uint32 sha256_k[64] =+ {0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,+ 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,+ 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,+ 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,+ 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,+ 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,+ 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,+ 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,+ 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,+ 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,+ 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,+ 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2};++uint64 sha512_k[80] =+ {0x428a2f98d728ae22ULL, 0x7137449123ef65cdULL,+ 0xb5c0fbcfec4d3b2fULL, 0xe9b5dba58189dbbcULL,+ 0x3956c25bf348b538ULL, 0x59f111f1b605d019ULL,+ 0x923f82a4af194f9bULL, 0xab1c5ed5da6d8118ULL,+ 0xd807aa98a3030242ULL, 0x12835b0145706fbeULL,+ 0x243185be4ee4b28cULL, 0x550c7dc3d5ffb4e2ULL,+ 0x72be5d74f27b896fULL, 0x80deb1fe3b1696b1ULL,+ 0x9bdc06a725c71235ULL, 0xc19bf174cf692694ULL,+ 0xe49b69c19ef14ad2ULL, 0xefbe4786384f25e3ULL,+ 0x0fc19dc68b8cd5b5ULL, 0x240ca1cc77ac9c65ULL,+ 0x2de92c6f592b0275ULL, 0x4a7484aa6ea6e483ULL,+ 0x5cb0a9dcbd41fbd4ULL, 0x76f988da831153b5ULL,+ 0x983e5152ee66dfabULL, 0xa831c66d2db43210ULL,+ 0xb00327c898fb213fULL, 0xbf597fc7beef0ee4ULL,+ 0xc6e00bf33da88fc2ULL, 0xd5a79147930aa725ULL,+ 0x06ca6351e003826fULL, 0x142929670a0e6e70ULL,+ 0x27b70a8546d22ffcULL, 0x2e1b21385c26c926ULL,+ 0x4d2c6dfc5ac42aedULL, 0x53380d139d95b3dfULL,+ 0x650a73548baf63deULL, 0x766a0abb3c77b2a8ULL,+ 0x81c2c92e47edaee6ULL, 0x92722c851482353bULL,+ 0xa2bfe8a14cf10364ULL, 0xa81a664bbc423001ULL,+ 0xc24b8b70d0f89791ULL, 0xc76c51a30654be30ULL,+ 0xd192e819d6ef5218ULL, 0xd69906245565a910ULL,+ 0xf40e35855771202aULL, 0x106aa07032bbd1b8ULL,+ 0x19a4c116b8d2d0c8ULL, 0x1e376c085141ab53ULL,+ 0x2748774cdf8eeb99ULL, 0x34b0bcb5e19b48a8ULL,+ 0x391c0cb3c5c95a63ULL, 0x4ed8aa4ae3418acbULL,+ 0x5b9cca4f7763e373ULL, 0x682e6ff3d6b2b8a3ULL,+ 0x748f82ee5defb2fcULL, 0x78a5636f43172f60ULL,+ 0x84c87814a1f0ab72ULL, 0x8cc702081a6439ecULL,+ 0x90befffa23631e28ULL, 0xa4506cebde82bde9ULL,+ 0xbef9a3f7b2c67915ULL, 0xc67178f2e372532bULL,+ 0xca273eceea26619cULL, 0xd186b8c721c0c207ULL,+ 0xeada7dd6cde0eb1eULL, 0xf57d4f7fee6ed178ULL,+ 0x06f067aa72176fbaULL, 0x0a637dc5a2c898a6ULL,+ 0x113f9804bef90daeULL, 0x1b710b35131c471bULL,+ 0x28db77f523047d84ULL, 0x32caab7b40c72493ULL,+ 0x3c9ebe0a15c9bebcULL, 0x431d67c49c100d4cULL,+ 0x4cc5d4becb3e42b6ULL, 0x597f299cfc657e2aULL,+ 0x5fcb6fab3ad6faecULL, 0x6c44198c4a475817ULL};++/* SHA-256 functions */++void sha256_transf(sha256_ctx *ctx, const unsigned char *message,+ unsigned int block_nb)+{+ uint32 w[64];+ uint32 wv[8];+ uint32 t1, t2;+ const unsigned char *sub_block;+ int i;++#ifndef UNROLL_LOOPS+ int j;+#endif++ for (i = 0; i < (int) block_nb; i++) {+ sub_block = message + (i << 6);++#ifndef UNROLL_LOOPS+ for (j = 0; j < 16; j++) {+ PACK32(&sub_block[j << 2], &w[j]);+ }++ for (j = 16; j < 64; j++) {+ SHA256_SCR(j);+ }++ for (j = 0; j < 8; j++) {+ wv[j] = ctx->h[j];+ }++ for (j = 0; j < 64; j++) {+ t1 = wv[7] + SHA256_F2(wv[4]) + CH(wv[4], wv[5], wv[6])+ + sha256_k[j] + w[j];+ t2 = SHA256_F1(wv[0]) + MAJ(wv[0], wv[1], wv[2]);+ wv[7] = wv[6];+ wv[6] = wv[5];+ wv[5] = wv[4];+ wv[4] = wv[3] + t1;+ wv[3] = wv[2];+ wv[2] = wv[1];+ wv[1] = wv[0];+ wv[0] = t1 + t2;+ }++ for (j = 0; j < 8; j++) {+ ctx->h[j] += wv[j];+ }+#else+ PACK32(&sub_block[ 0], &w[ 0]); PACK32(&sub_block[ 4], &w[ 1]);+ PACK32(&sub_block[ 8], &w[ 2]); PACK32(&sub_block[12], &w[ 3]);+ PACK32(&sub_block[16], &w[ 4]); PACK32(&sub_block[20], &w[ 5]);+ PACK32(&sub_block[24], &w[ 6]); PACK32(&sub_block[28], &w[ 7]);+ PACK32(&sub_block[32], &w[ 8]); PACK32(&sub_block[36], &w[ 9]);+ PACK32(&sub_block[40], &w[10]); PACK32(&sub_block[44], &w[11]);+ PACK32(&sub_block[48], &w[12]); PACK32(&sub_block[52], &w[13]);+ PACK32(&sub_block[56], &w[14]); PACK32(&sub_block[60], &w[15]);++ SHA256_SCR(16); SHA256_SCR(17); SHA256_SCR(18); SHA256_SCR(19);+ SHA256_SCR(20); SHA256_SCR(21); SHA256_SCR(22); SHA256_SCR(23);+ SHA256_SCR(24); SHA256_SCR(25); SHA256_SCR(26); SHA256_SCR(27);+ SHA256_SCR(28); SHA256_SCR(29); SHA256_SCR(30); SHA256_SCR(31);+ SHA256_SCR(32); SHA256_SCR(33); SHA256_SCR(34); SHA256_SCR(35);+ SHA256_SCR(36); SHA256_SCR(37); SHA256_SCR(38); SHA256_SCR(39);+ SHA256_SCR(40); SHA256_SCR(41); SHA256_SCR(42); SHA256_SCR(43);+ SHA256_SCR(44); SHA256_SCR(45); SHA256_SCR(46); SHA256_SCR(47);+ SHA256_SCR(48); SHA256_SCR(49); SHA256_SCR(50); SHA256_SCR(51);+ SHA256_SCR(52); SHA256_SCR(53); SHA256_SCR(54); SHA256_SCR(55);+ SHA256_SCR(56); SHA256_SCR(57); SHA256_SCR(58); SHA256_SCR(59);+ SHA256_SCR(60); SHA256_SCR(61); SHA256_SCR(62); SHA256_SCR(63);++ wv[0] = ctx->h[0]; wv[1] = ctx->h[1];+ wv[2] = ctx->h[2]; wv[3] = ctx->h[3];+ wv[4] = ctx->h[4]; wv[5] = ctx->h[5];+ wv[6] = ctx->h[6]; wv[7] = ctx->h[7];++ SHA256_EXP(0,1,2,3,4,5,6,7, 0); SHA256_EXP(7,0,1,2,3,4,5,6, 1);+ SHA256_EXP(6,7,0,1,2,3,4,5, 2); SHA256_EXP(5,6,7,0,1,2,3,4, 3);+ SHA256_EXP(4,5,6,7,0,1,2,3, 4); SHA256_EXP(3,4,5,6,7,0,1,2, 5);+ SHA256_EXP(2,3,4,5,6,7,0,1, 6); SHA256_EXP(1,2,3,4,5,6,7,0, 7);+ SHA256_EXP(0,1,2,3,4,5,6,7, 8); SHA256_EXP(7,0,1,2,3,4,5,6, 9);+ SHA256_EXP(6,7,0,1,2,3,4,5,10); SHA256_EXP(5,6,7,0,1,2,3,4,11);+ SHA256_EXP(4,5,6,7,0,1,2,3,12); SHA256_EXP(3,4,5,6,7,0,1,2,13);+ SHA256_EXP(2,3,4,5,6,7,0,1,14); SHA256_EXP(1,2,3,4,5,6,7,0,15);+ SHA256_EXP(0,1,2,3,4,5,6,7,16); SHA256_EXP(7,0,1,2,3,4,5,6,17);+ SHA256_EXP(6,7,0,1,2,3,4,5,18); SHA256_EXP(5,6,7,0,1,2,3,4,19);+ SHA256_EXP(4,5,6,7,0,1,2,3,20); SHA256_EXP(3,4,5,6,7,0,1,2,21);+ SHA256_EXP(2,3,4,5,6,7,0,1,22); SHA256_EXP(1,2,3,4,5,6,7,0,23);+ SHA256_EXP(0,1,2,3,4,5,6,7,24); SHA256_EXP(7,0,1,2,3,4,5,6,25);+ SHA256_EXP(6,7,0,1,2,3,4,5,26); SHA256_EXP(5,6,7,0,1,2,3,4,27);+ SHA256_EXP(4,5,6,7,0,1,2,3,28); SHA256_EXP(3,4,5,6,7,0,1,2,29);+ SHA256_EXP(2,3,4,5,6,7,0,1,30); SHA256_EXP(1,2,3,4,5,6,7,0,31);+ SHA256_EXP(0,1,2,3,4,5,6,7,32); SHA256_EXP(7,0,1,2,3,4,5,6,33);+ SHA256_EXP(6,7,0,1,2,3,4,5,34); SHA256_EXP(5,6,7,0,1,2,3,4,35);+ SHA256_EXP(4,5,6,7,0,1,2,3,36); SHA256_EXP(3,4,5,6,7,0,1,2,37);+ SHA256_EXP(2,3,4,5,6,7,0,1,38); SHA256_EXP(1,2,3,4,5,6,7,0,39);+ SHA256_EXP(0,1,2,3,4,5,6,7,40); SHA256_EXP(7,0,1,2,3,4,5,6,41);+ SHA256_EXP(6,7,0,1,2,3,4,5,42); SHA256_EXP(5,6,7,0,1,2,3,4,43);+ SHA256_EXP(4,5,6,7,0,1,2,3,44); SHA256_EXP(3,4,5,6,7,0,1,2,45);+ SHA256_EXP(2,3,4,5,6,7,0,1,46); SHA256_EXP(1,2,3,4,5,6,7,0,47);+ SHA256_EXP(0,1,2,3,4,5,6,7,48); SHA256_EXP(7,0,1,2,3,4,5,6,49);+ SHA256_EXP(6,7,0,1,2,3,4,5,50); SHA256_EXP(5,6,7,0,1,2,3,4,51);+ SHA256_EXP(4,5,6,7,0,1,2,3,52); SHA256_EXP(3,4,5,6,7,0,1,2,53);+ SHA256_EXP(2,3,4,5,6,7,0,1,54); SHA256_EXP(1,2,3,4,5,6,7,0,55);+ SHA256_EXP(0,1,2,3,4,5,6,7,56); SHA256_EXP(7,0,1,2,3,4,5,6,57);+ SHA256_EXP(6,7,0,1,2,3,4,5,58); SHA256_EXP(5,6,7,0,1,2,3,4,59);+ SHA256_EXP(4,5,6,7,0,1,2,3,60); SHA256_EXP(3,4,5,6,7,0,1,2,61);+ SHA256_EXP(2,3,4,5,6,7,0,1,62); SHA256_EXP(1,2,3,4,5,6,7,0,63);++ ctx->h[0] += wv[0]; ctx->h[1] += wv[1];+ ctx->h[2] += wv[2]; ctx->h[3] += wv[3];+ ctx->h[4] += wv[4]; ctx->h[5] += wv[5];+ ctx->h[6] += wv[6]; ctx->h[7] += wv[7];+#endif /* !UNROLL_LOOPS */+ }+}++void sha256(const unsigned char *message, unsigned int len, unsigned char *digest)+{+ sha256_ctx ctx;++ sha256_init(&ctx);+ sha256_update(&ctx, message, len);+ sha256_final(&ctx, digest);+}++void sha256_init(sha256_ctx *ctx)+{+#ifndef UNROLL_LOOPS+ int i;+ for (i = 0; i < 8; i++) {+ ctx->h[i] = sha256_h0[i];+ }+#else+ ctx->h[0] = sha256_h0[0]; ctx->h[1] = sha256_h0[1];+ ctx->h[2] = sha256_h0[2]; ctx->h[3] = sha256_h0[3];+ ctx->h[4] = sha256_h0[4]; ctx->h[5] = sha256_h0[5];+ ctx->h[6] = sha256_h0[6]; ctx->h[7] = sha256_h0[7];+#endif /* !UNROLL_LOOPS */++ ctx->len = 0;+ ctx->tot_len = 0;+}++void sha256_update(sha256_ctx *ctx, const unsigned char *message,+ unsigned int len)+{+ unsigned int block_nb;+ unsigned int new_len, rem_len, tmp_len;+ const unsigned char *shifted_message;++ tmp_len = SHA256_BLOCK_SIZE - ctx->len;+ rem_len = len < tmp_len ? len : tmp_len;++ memcpy(&ctx->block[ctx->len], message, rem_len);++ if (ctx->len + len < SHA256_BLOCK_SIZE) {+ ctx->len += len;+ return;+ }++ new_len = len - rem_len;+ block_nb = new_len / SHA256_BLOCK_SIZE;++ shifted_message = message + rem_len;++ sha256_transf(ctx, ctx->block, 1);+ sha256_transf(ctx, shifted_message, block_nb);++ rem_len = new_len % SHA256_BLOCK_SIZE;++ memcpy(ctx->block, &shifted_message[block_nb << 6],+ rem_len);++ ctx->len = rem_len;+ ctx->tot_len += (block_nb + 1) << 6;+}++void sha256_final(sha256_ctx *ctx, unsigned char *digest)+{+ unsigned int block_nb;+ unsigned int pm_len;+ unsigned int len_b;++#ifndef UNROLL_LOOPS+ int i;+#endif++ block_nb = (1 + ((SHA256_BLOCK_SIZE - 9)+ < (ctx->len % SHA256_BLOCK_SIZE)));++ len_b = (ctx->tot_len + ctx->len) << 3;+ pm_len = block_nb << 6;++ memset(ctx->block + ctx->len, 0, pm_len - ctx->len);+ ctx->block[ctx->len] = 0x80;+ UNPACK32(len_b, ctx->block + pm_len - 4);++ sha256_transf(ctx, ctx->block, block_nb);++#ifndef UNROLL_LOOPS+ for (i = 0 ; i < 8; i++) {+ UNPACK32(ctx->h[i], &digest[i << 2]);+ }+#else+ UNPACK32(ctx->h[0], &digest[ 0]);+ UNPACK32(ctx->h[1], &digest[ 4]);+ UNPACK32(ctx->h[2], &digest[ 8]);+ UNPACK32(ctx->h[3], &digest[12]);+ UNPACK32(ctx->h[4], &digest[16]);+ UNPACK32(ctx->h[5], &digest[20]);+ UNPACK32(ctx->h[6], &digest[24]);+ UNPACK32(ctx->h[7], &digest[28]);+#endif /* !UNROLL_LOOPS */+}++/* SHA-512 functions */++void sha512_transf(sha512_ctx *ctx, const unsigned char *message,+ unsigned int block_nb)+{+ uint64 w[80];+ uint64 wv[8];+ uint64 t1, t2;+ const unsigned char *sub_block;+ int i, j;++ for (i = 0; i < (int) block_nb; i++) {+ sub_block = message + (i << 7);++#ifndef UNROLL_LOOPS+ for (j = 0; j < 16; j++) {+ PACK64(&sub_block[j << 3], &w[j]);+ }++ for (j = 16; j < 80; j++) {+ SHA512_SCR(j);+ }++ for (j = 0; j < 8; j++) {+ wv[j] = ctx->h[j];+ }++ for (j = 0; j < 80; j++) {+ t1 = wv[7] + SHA512_F2(wv[4]) + CH(wv[4], wv[5], wv[6])+ + sha512_k[j] + w[j];+ t2 = SHA512_F1(wv[0]) + MAJ(wv[0], wv[1], wv[2]);+ wv[7] = wv[6];+ wv[6] = wv[5];+ wv[5] = wv[4];+ wv[4] = wv[3] + t1;+ wv[3] = wv[2];+ wv[2] = wv[1];+ wv[1] = wv[0];+ wv[0] = t1 + t2;+ }++ for (j = 0; j < 8; j++) {+ ctx->h[j] += wv[j];+ }+#else+ PACK64(&sub_block[ 0], &w[ 0]); PACK64(&sub_block[ 8], &w[ 1]);+ PACK64(&sub_block[ 16], &w[ 2]); PACK64(&sub_block[ 24], &w[ 3]);+ PACK64(&sub_block[ 32], &w[ 4]); PACK64(&sub_block[ 40], &w[ 5]);+ PACK64(&sub_block[ 48], &w[ 6]); PACK64(&sub_block[ 56], &w[ 7]);+ PACK64(&sub_block[ 64], &w[ 8]); PACK64(&sub_block[ 72], &w[ 9]);+ PACK64(&sub_block[ 80], &w[10]); PACK64(&sub_block[ 88], &w[11]);+ PACK64(&sub_block[ 96], &w[12]); PACK64(&sub_block[104], &w[13]);+ PACK64(&sub_block[112], &w[14]); PACK64(&sub_block[120], &w[15]);++ SHA512_SCR(16); SHA512_SCR(17); SHA512_SCR(18); SHA512_SCR(19);+ SHA512_SCR(20); SHA512_SCR(21); SHA512_SCR(22); SHA512_SCR(23);+ SHA512_SCR(24); SHA512_SCR(25); SHA512_SCR(26); SHA512_SCR(27);+ SHA512_SCR(28); SHA512_SCR(29); SHA512_SCR(30); SHA512_SCR(31);+ SHA512_SCR(32); SHA512_SCR(33); SHA512_SCR(34); SHA512_SCR(35);+ SHA512_SCR(36); SHA512_SCR(37); SHA512_SCR(38); SHA512_SCR(39);+ SHA512_SCR(40); SHA512_SCR(41); SHA512_SCR(42); SHA512_SCR(43);+ SHA512_SCR(44); SHA512_SCR(45); SHA512_SCR(46); SHA512_SCR(47);+ SHA512_SCR(48); SHA512_SCR(49); SHA512_SCR(50); SHA512_SCR(51);+ SHA512_SCR(52); SHA512_SCR(53); SHA512_SCR(54); SHA512_SCR(55);+ SHA512_SCR(56); SHA512_SCR(57); SHA512_SCR(58); SHA512_SCR(59);+ SHA512_SCR(60); SHA512_SCR(61); SHA512_SCR(62); SHA512_SCR(63);+ SHA512_SCR(64); SHA512_SCR(65); SHA512_SCR(66); SHA512_SCR(67);+ SHA512_SCR(68); SHA512_SCR(69); SHA512_SCR(70); SHA512_SCR(71);+ SHA512_SCR(72); SHA512_SCR(73); SHA512_SCR(74); SHA512_SCR(75);+ SHA512_SCR(76); SHA512_SCR(77); SHA512_SCR(78); SHA512_SCR(79);++ wv[0] = ctx->h[0]; wv[1] = ctx->h[1];+ wv[2] = ctx->h[2]; wv[3] = ctx->h[3];+ wv[4] = ctx->h[4]; wv[5] = ctx->h[5];+ wv[6] = ctx->h[6]; wv[7] = ctx->h[7];++ j = 0;++ do {+ SHA512_EXP(0,1,2,3,4,5,6,7,j); j++;+ SHA512_EXP(7,0,1,2,3,4,5,6,j); j++;+ SHA512_EXP(6,7,0,1,2,3,4,5,j); j++;+ SHA512_EXP(5,6,7,0,1,2,3,4,j); j++;+ SHA512_EXP(4,5,6,7,0,1,2,3,j); j++;+ SHA512_EXP(3,4,5,6,7,0,1,2,j); j++;+ SHA512_EXP(2,3,4,5,6,7,0,1,j); j++;+ SHA512_EXP(1,2,3,4,5,6,7,0,j); j++;+ } while (j < 80);++ ctx->h[0] += wv[0]; ctx->h[1] += wv[1];+ ctx->h[2] += wv[2]; ctx->h[3] += wv[3];+ ctx->h[4] += wv[4]; ctx->h[5] += wv[5];+ ctx->h[6] += wv[6]; ctx->h[7] += wv[7];+#endif /* !UNROLL_LOOPS */+ }+}++void sha512(const unsigned char *message, unsigned int len,+ unsigned char *digest)+{+ sha512_ctx ctx;++ sha512_init(&ctx);+ sha512_update(&ctx, message, len);+ sha512_final(&ctx, digest);+}++void sha512_init(sha512_ctx *ctx)+{+#ifndef UNROLL_LOOPS+ int i;+ for (i = 0; i < 8; i++) {+ ctx->h[i] = sha512_h0[i];+ }+#else+ ctx->h[0] = sha512_h0[0]; ctx->h[1] = sha512_h0[1];+ ctx->h[2] = sha512_h0[2]; ctx->h[3] = sha512_h0[3];+ ctx->h[4] = sha512_h0[4]; ctx->h[5] = sha512_h0[5];+ ctx->h[6] = sha512_h0[6]; ctx->h[7] = sha512_h0[7];+#endif /* !UNROLL_LOOPS */++ ctx->len = 0;+ ctx->tot_len = 0;+}++void sha512_update(sha512_ctx *ctx, const unsigned char *message,+ unsigned int len)+{+ unsigned int block_nb;+ unsigned int new_len, rem_len, tmp_len;+ const unsigned char *shifted_message;++ tmp_len = SHA512_BLOCK_SIZE - ctx->len;+ rem_len = len < tmp_len ? len : tmp_len;++ memcpy(&ctx->block[ctx->len], message, rem_len);++ if (ctx->len + len < SHA512_BLOCK_SIZE) {+ ctx->len += len;+ return;+ }++ new_len = len - rem_len;+ block_nb = new_len / SHA512_BLOCK_SIZE;++ shifted_message = message + rem_len;++ sha512_transf(ctx, ctx->block, 1);+ sha512_transf(ctx, shifted_message, block_nb);++ rem_len = new_len % SHA512_BLOCK_SIZE;++ memcpy(ctx->block, &shifted_message[block_nb << 7],+ rem_len);++ ctx->len = rem_len;+ ctx->tot_len += (block_nb + 1) << 7;+}++void sha512_final(sha512_ctx *ctx, unsigned char *digest)+{+ unsigned int block_nb;+ unsigned int pm_len;+ unsigned int len_b;++#ifndef UNROLL_LOOPS+ int i;+#endif++ block_nb = 1 + ((SHA512_BLOCK_SIZE - 17)+ < (ctx->len % SHA512_BLOCK_SIZE));++ len_b = (ctx->tot_len + ctx->len) << 3;+ pm_len = block_nb << 7;++ memset(ctx->block + ctx->len, 0, pm_len - ctx->len);+ ctx->block[ctx->len] = 0x80;+ UNPACK32(len_b, ctx->block + pm_len - 4);++ sha512_transf(ctx, ctx->block, block_nb);++#ifndef UNROLL_LOOPS+ for (i = 0 ; i < 8; i++) {+ UNPACK64(ctx->h[i], &digest[i << 3]);+ }+#else+ UNPACK64(ctx->h[0], &digest[ 0]);+ UNPACK64(ctx->h[1], &digest[ 8]);+ UNPACK64(ctx->h[2], &digest[16]);+ UNPACK64(ctx->h[3], &digest[24]);+ UNPACK64(ctx->h[4], &digest[32]);+ UNPACK64(ctx->h[5], &digest[40]);+ UNPACK64(ctx->h[6], &digest[48]);+ UNPACK64(ctx->h[7], &digest[56]);+#endif /* !UNROLL_LOOPS */+}++/* SHA-384 functions */++void sha384(const unsigned char *message, unsigned int len,+ unsigned char *digest)+{+ sha384_ctx ctx;++ sha384_init(&ctx);+ sha384_update(&ctx, message, len);+ sha384_final(&ctx, digest);+}++void sha384_init(sha384_ctx *ctx)+{+#ifndef UNROLL_LOOPS+ int i;+ for (i = 0; i < 8; i++) {+ ctx->h[i] = sha384_h0[i];+ }+#else+ ctx->h[0] = sha384_h0[0]; ctx->h[1] = sha384_h0[1];+ ctx->h[2] = sha384_h0[2]; ctx->h[3] = sha384_h0[3];+ ctx->h[4] = sha384_h0[4]; ctx->h[5] = sha384_h0[5];+ ctx->h[6] = sha384_h0[6]; ctx->h[7] = sha384_h0[7];+#endif /* !UNROLL_LOOPS */++ ctx->len = 0;+ ctx->tot_len = 0;+}++void sha384_update(sha384_ctx *ctx, const unsigned char *message,+ unsigned int len)+{+ unsigned int block_nb;+ unsigned int new_len, rem_len, tmp_len;+ const unsigned char *shifted_message;++ tmp_len = SHA384_BLOCK_SIZE - ctx->len;+ rem_len = len < tmp_len ? len : tmp_len;++ memcpy(&ctx->block[ctx->len], message, rem_len);++ if (ctx->len + len < SHA384_BLOCK_SIZE) {+ ctx->len += len;+ return;+ }++ new_len = len - rem_len;+ block_nb = new_len / SHA384_BLOCK_SIZE;++ shifted_message = message + rem_len;++ sha512_transf(ctx, ctx->block, 1);+ sha512_transf(ctx, shifted_message, block_nb);++ rem_len = new_len % SHA384_BLOCK_SIZE;++ memcpy(ctx->block, &shifted_message[block_nb << 7],+ rem_len);++ ctx->len = rem_len;+ ctx->tot_len += (block_nb + 1) << 7;+}++void sha384_final(sha384_ctx *ctx, unsigned char *digest)+{+ unsigned int block_nb;+ unsigned int pm_len;+ unsigned int len_b;++#ifndef UNROLL_LOOPS+ int i;+#endif++ block_nb = (1 + ((SHA384_BLOCK_SIZE - 17)+ < (ctx->len % SHA384_BLOCK_SIZE)));++ len_b = (ctx->tot_len + ctx->len) << 3;+ pm_len = block_nb << 7;++ memset(ctx->block + ctx->len, 0, pm_len - ctx->len);+ ctx->block[ctx->len] = 0x80;+ UNPACK32(len_b, ctx->block + pm_len - 4);++ sha512_transf(ctx, ctx->block, block_nb);++#ifndef UNROLL_LOOPS+ for (i = 0 ; i < 6; i++) {+ UNPACK64(ctx->h[i], &digest[i << 3]);+ }+#else+ UNPACK64(ctx->h[0], &digest[ 0]);+ UNPACK64(ctx->h[1], &digest[ 8]);+ UNPACK64(ctx->h[2], &digest[16]);+ UNPACK64(ctx->h[3], &digest[24]);+ UNPACK64(ctx->h[4], &digest[32]);+ UNPACK64(ctx->h[5], &digest[40]);+#endif /* !UNROLL_LOOPS */+}++/* SHA-224 functions */++void sha224(const unsigned char *message, unsigned int len,+ unsigned char *digest)+{+ sha224_ctx ctx;++ sha224_init(&ctx);+ sha224_update(&ctx, message, len);+ sha224_final(&ctx, digest);+}++void sha224_init(sha224_ctx *ctx)+{+#ifndef UNROLL_LOOPS+ int i;+ for (i = 0; i < 8; i++) {+ ctx->h[i] = sha224_h0[i];+ }+#else+ ctx->h[0] = sha224_h0[0]; ctx->h[1] = sha224_h0[1];+ ctx->h[2] = sha224_h0[2]; ctx->h[3] = sha224_h0[3];+ ctx->h[4] = sha224_h0[4]; ctx->h[5] = sha224_h0[5];+ ctx->h[6] = sha224_h0[6]; ctx->h[7] = sha224_h0[7];+#endif /* !UNROLL_LOOPS */++ ctx->len = 0;+ ctx->tot_len = 0;+}++void sha224_update(sha224_ctx *ctx, const unsigned char *message,+ unsigned int len)+{+ unsigned int block_nb;+ unsigned int new_len, rem_len, tmp_len;+ const unsigned char *shifted_message;++ tmp_len = SHA224_BLOCK_SIZE - ctx->len;+ rem_len = len < tmp_len ? len : tmp_len;++ memcpy(&ctx->block[ctx->len], message, rem_len);++ if (ctx->len + len < SHA224_BLOCK_SIZE) {+ ctx->len += len;+ return;+ }++ new_len = len - rem_len;+ block_nb = new_len / SHA224_BLOCK_SIZE;++ shifted_message = message + rem_len;++ sha256_transf(ctx, ctx->block, 1);+ sha256_transf(ctx, shifted_message, block_nb);++ rem_len = new_len % SHA224_BLOCK_SIZE;++ memcpy(ctx->block, &shifted_message[block_nb << 6],+ rem_len);++ ctx->len = rem_len;+ ctx->tot_len += (block_nb + 1) << 6;+}++void sha224_final(sha224_ctx *ctx, unsigned char *digest)+{+ unsigned int block_nb;+ unsigned int pm_len;+ unsigned int len_b;++#ifndef UNROLL_LOOPS+ int i;+#endif++ block_nb = (1 + ((SHA224_BLOCK_SIZE - 9)+ < (ctx->len % SHA224_BLOCK_SIZE)));++ len_b = (ctx->tot_len + ctx->len) << 3;+ pm_len = block_nb << 6;++ memset(ctx->block + ctx->len, 0, pm_len - ctx->len);+ ctx->block[ctx->len] = 0x80;+ UNPACK32(len_b, ctx->block + pm_len - 4);++ sha256_transf(ctx, ctx->block, block_nb);++#ifndef UNROLL_LOOPS+ for (i = 0 ; i < 7; i++) {+ UNPACK32(ctx->h[i], &digest[i << 2]);+ }+#else+ UNPACK32(ctx->h[0], &digest[ 0]);+ UNPACK32(ctx->h[1], &digest[ 4]);+ UNPACK32(ctx->h[2], &digest[ 8]);+ UNPACK32(ctx->h[3], &digest[12]);+ UNPACK32(ctx->h[4], &digest[16]);+ UNPACK32(ctx->h[5], &digest[20]);+ UNPACK32(ctx->h[6], &digest[24]);+#endif /* !UNROLL_LOOPS */+}++#ifdef TEST_VECTORS++/* FIPS 180-2 Validation tests */++#include <stdio.h>+#include <stdlib.h>++void test(const unsigned char *vector, unsigned char *digest,+ unsigned int digest_size)+{+ unsigned char output[2 * SHA512_DIGEST_SIZE + 1];+ int i;++ output[2 * digest_size] = '\0';++ for (i = 0; i < (int) digest_size ; i++) {+ sprintf((char *) output + 2 * i, "%02x", digest[i]);+ }++ printf("H: %s\n", output);+ if (strcmp((char *) vector, (char *) output)) {+ fprintf(stderr, "Test failed.\n");+ exit(EXIT_FAILURE);+ }+}++int main()+{+ static const unsigned char *vectors[4][3] =+ { /* SHA-224 */+ {+ "23097d223405d8228642a477bda255b32aadbce4bda0b3f7e36c9da7",+ "75388b16512776cc5dba5da1fd890150b0c6455cb4f58b1952522525",+ "20794655980c91d8bbb4c1ea97618a4bf03f42581948b2ee4ee7ad67",+ },+ /* SHA-256 */+ {+ "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",+ "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1",+ "cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0",+ },+ /* SHA-384 */+ {+ "cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed"+ "8086072ba1e7cc2358baeca134c825a7",+ "09330c33f71147e83d192fc782cd1b4753111b173b3b05d22fa08086e3b0f712"+ "fcc7c71a557e2db966c3e9fa91746039",+ "9d0e1809716474cb086e834e310a4a1ced149e9c00f248527972cec5704c2a5b"+ "07b8b3dc38ecc4ebae97ddd87f3d8985",+ },+ /* SHA-512 */+ {+ "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a"+ "2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f",+ "8e959b75dae313da8cf4f72814fc143f8f7779c6eb9f7fa17299aeadb6889018"+ "501d289e4900f7e4331b99dec4b5433ac7d329eeb6dd26545e96e55b874be909",+ "e718483d0ce769644e2e42c7bc15b4638e1f98b13b2044285632a803afa973eb"+ "de0ff244877ea60a4cb0432ce577c31beb009c5c2c49aa2e4eadb217ad8cc09b"+ }+ };++ static const unsigned char message1[] = "abc";+ static const unsigned char message2a[] = "abcdbcdecdefdefgefghfghighijhi"+ "jkijkljklmklmnlmnomnopnopq";+ static const unsigned char message2b[] =+ "abcdefghbcdefghicdefghijdefghijkefghij"+ "klfghijklmghijklmnhijklmnoijklmnopjklm"+ "nopqklmnopqrlmnopqrsmnopqrstnopqrstu";+ unsigned char *message3;+ unsigned int message3_len = 1000000;+ unsigned char digest[SHA512_DIGEST_SIZE];++ message3 = malloc(message3_len);+ if (message3 == NULL) {+ fprintf(stderr, "Can't allocate memory\n");+ return -1;+ }+ memset(message3, 'a', message3_len);++ printf("SHA-2 FIPS 180-2 Validation tests\n\n");+ printf("SHA-224 Test vectors\n");++ sha224(message1, strlen((char *) message1), digest);+ test(vectors[0][0], digest, SHA224_DIGEST_SIZE);+ sha224(message2a, strlen((char *) message2a), digest);+ test(vectors[0][1], digest, SHA224_DIGEST_SIZE);+ sha224(message3, message3_len, digest);+ test(vectors[0][2], digest, SHA224_DIGEST_SIZE);+ printf("\n");++ printf("SHA-256 Test vectors\n");++ sha256(message1, strlen((char *) message1), digest);+ test(vectors[1][0], digest, SHA256_DIGEST_SIZE);+ sha256(message2a, strlen((char *) message2a), digest);+ test(vectors[1][1], digest, SHA256_DIGEST_SIZE);+ sha256(message3, message3_len, digest);+ test(vectors[1][2], digest, SHA256_DIGEST_SIZE);+ printf("\n");++ printf("SHA-384 Test vectors\n");++ sha384(message1, strlen((char *) message1), digest);+ test(vectors[2][0], digest, SHA384_DIGEST_SIZE);+ sha384(message2b, strlen((char *) message2b), digest);+ test(vectors[2][1], digest, SHA384_DIGEST_SIZE);+ sha384(message3, message3_len, digest);+ test(vectors[2][2], digest, SHA384_DIGEST_SIZE);+ printf("\n");++ printf("SHA-512 Test vectors\n");++ sha512(message1, strlen((char *) message1), digest);+ test(vectors[3][0], digest, SHA512_DIGEST_SIZE);+ sha512(message2b, strlen((char *) message2b), digest);+ test(vectors[3][1], digest, SHA512_DIGEST_SIZE);+ sha512(message3, message3_len, digest);+ test(vectors[3][2], digest, SHA512_DIGEST_SIZE);+ printf("\n");++ printf("All tests passed.\n");++ return 0;+}++#endif /* TEST_VECTORS */+
@@ -0,0 +1,108 @@+/*+ * FIPS 180-2 SHA-224/256/384/512 implementation+ * Last update: 02/02/2007+ * Issue date: 04/30/2005+ *+ * Copyright (C) 2005, 2007 Olivier Gay <olivier.gay@a3.epfl.ch>+ * All rights reserved.+ *+ * Redistribution and use in source and binary forms, with or without+ * modification, are permitted provided that the following conditions+ * are met:+ * 1. Redistributions of source code must retain the above copyright+ * notice, this list of conditions and the following disclaimer.+ * 2. Redistributions in binary form must reproduce the above copyright+ * notice, this list of conditions and the following disclaimer in the+ * documentation and/or other materials provided with the distribution.+ * 3. Neither the name of the project nor the names of its contributors+ * may be used to endorse or promote products derived from this software+ * without specific prior written permission.+ *+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE+ * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF+ * SUCH DAMAGE.+ */++#ifndef SHA2_H+#define SHA2_H++#define SHA224_DIGEST_SIZE ( 224 / 8)+#define SHA256_DIGEST_SIZE ( 256 / 8)+#define SHA384_DIGEST_SIZE ( 384 / 8)+#define SHA512_DIGEST_SIZE ( 512 / 8)++#define SHA256_BLOCK_SIZE ( 512 / 8)+#define SHA512_BLOCK_SIZE (1024 / 8)+#define SHA384_BLOCK_SIZE SHA512_BLOCK_SIZE+#define SHA224_BLOCK_SIZE SHA256_BLOCK_SIZE++#ifndef SHA2_TYPES+#define SHA2_TYPES+typedef unsigned char uint8;+typedef unsigned int uint32;+typedef unsigned long long uint64;+#endif++#ifdef __cplusplus+extern "C" {+#endif++typedef struct {+ unsigned int tot_len;+ unsigned int len;+ unsigned char block[2 * SHA256_BLOCK_SIZE];+ uint32 h[8];+} sha256_ctx;++typedef struct {+ unsigned int tot_len;+ unsigned int len;+ unsigned char block[2 * SHA512_BLOCK_SIZE];+ uint64 h[8];+} sha512_ctx;++typedef sha512_ctx sha384_ctx;+typedef sha256_ctx sha224_ctx;++void sha224_init(sha224_ctx *ctx);+void sha224_update(sha224_ctx *ctx, const unsigned char *message,+ unsigned int len);+void sha224_final(sha224_ctx *ctx, unsigned char *digest);+void sha224(const unsigned char *message, unsigned int len,+ unsigned char *digest);++void sha256_init(sha256_ctx * ctx);+void sha256_update(sha256_ctx *ctx, const unsigned char *message,+ unsigned int len);+void sha256_final(sha256_ctx *ctx, unsigned char *digest);+void sha256(const unsigned char *message, unsigned int len,+ unsigned char *digest);++void sha384_init(sha384_ctx *ctx);+void sha384_update(sha384_ctx *ctx, const unsigned char *message,+ unsigned int len);+void sha384_final(sha384_ctx *ctx, unsigned char *digest);+void sha384(const unsigned char *message, unsigned int len,+ unsigned char *digest);++void sha512_init(sha512_ctx *ctx);+void sha512_update(sha512_ctx *ctx, const unsigned char *message,+ unsigned int len);+void sha512_final(sha512_ctx *ctx, unsigned char *digest);+void sha512(const unsigned char *message, unsigned int len,+ unsigned char *digest);++#ifdef __cplusplus+}+#endif++#endif /* !SHA2_H */+
@@ -0,0 +1,172 @@+% Copyright (C) 2003 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\begin{code}+module Darcs.ArgumentDefaults ( get_default_flags ) where+import Data.Maybe ( catMaybes, listToMaybe, mapMaybe )++import Darcs.Arguments ( DarcsFlag,+ DarcsOption( DarcsArgOption, DarcsNoArgOption, DarcsMultipleChoiceOption ),+ arein, isin )+import Darcs.Commands ( CommandControl( Command_data ),+ command_alloptions )+import Darcs.Commands.Help ( command_control_list )+import Darcs.Repository.Prefs ( get_global, get_preflist )+\end{code}++\paragraph{defaults}\label{defaults}++Default values for darcs commands can be configured on a per-repository+basis by editing (and possibly creating) the \verb!_darcs/prefs/defaults!+file. Each line of this file has the following form:+\begin{verbatim}+COMMAND FLAG VALUE+\end{verbatim}+where \verb!COMMAND! is either the name of the command to which the default+applies, or \verb!ALL! to indicate that the default applies to all commands+accepting that flag. The \verb!FLAG! term is the name of the long argument+option without the ``\verb!--!'', i.e.\ \verb!verbose! rather than+\verb!--verbose!. Finally, the \verb!VALUE! option can be omitted if the+flag is one such as \verb!verbose! that doesn't involve a value.+If the value has spaces in it, use single quotes, not double quotes, to surround it. +Each line only takes one flag. To set multiple defaults for the same+command (or for \verb!ALL! commands), use multiple lines.++Note that the use of \verb|ALL| easily can have unpredicted consequences,+especially if commands in newer versions of darcs accepts flags that they+didn't in previous versions. A command like \verb|obliterate| could be+devastating with the ``wrong'' flags (for example --all). Only use safe+flags with \verb|ALL|.++\begin{tabular}{ll}+{\tt \verb!~/.darcs/defaults!} & provides defaults for this user account, on MS Windows~\ref{ms_win} \\+{\tt \verb!repo/_darcs/prefs/defaults!} & provides defaults for one project,\\+ & overrules changes per user \\+\end{tabular}++For example, if your system clock is bizarre, you could instruct darcs to+always ignore the file modification times by adding the following line to+your \verb!_darcs/prefs/defaults! file. (Note that this would have to be+done for each repository!)+\begin{verbatim}+ALL ignore-times+\end{verbatim}++If you never want to run a test when recording to a particular repository+(but still want to do so when running+\verb'check' on that repository), and like to name+all your patches ``Stupid patch'', you could use the following:+\begin{verbatim}+record no-test+record patch-name Stupid patch+\end{verbatim}++If you would like a command to be run every time patches are recorded+in a particular repository (for example if you have one central+repository, that all developers contribute to), then you can set apply+to always run a command when apply is successful. For example, if you+need to make sure that the files in the repository have the correct+access rights you might use the following. There are two things+to note about using darcs this way:+\begin{itemize}+\item Without the second line you will get errors, because the sub+ process that runs apply cannot prompt interactively.+\item Whatever script is run by the post apply command should not be+ added to the repository with \verb!darcs add!; doing so would+ allow people to modify that file and then run arbitrary scripts on+ your main repository, possibly damaging or violating security.+\end{itemize}+\begin{verbatim}+apply posthook chmod -R a+r *+apply run-posthook+\end{verbatim}++Similarly, if you need a command to run automatically before darcs+performs an action you can use a prehook. Using prehooks it could be+possible to canonicalize line endings before recording patches.++There are some options which are meant specifically for use in+\verb!_darcs/prefs/defaults!. One of them is \verb!--disable!. As the name+suggests, this option will disable every command that got it as argument. So,+if you are afraid that you could damage your repositories by inadvertent use of+a command like amend-record, add the following line to+\verb!_darcs/prefs/defaults!:+\begin{verbatim}+amend-record disable+\end{verbatim}++Also, a global preferences file can be created with the name+\verb!.darcs/defaults! in your home directory, on MS Windows~\ref{ms_win}. +Options present there will be added to the repository-specific preferences.+If they conflict with repository-specific options, the repository-specific+ones will take precedence.++\begin{code}+get_default_flags :: String -> [DarcsOption] -> [DarcsFlag] -> IO [DarcsFlag]+get_default_flags com com_opts already = do+ repo_defs <- default_content $ get_preflist "defaults"+ global_defs <- default_content $ get_global "defaults"+ let repo_flags = get_flags_from com com_opts already repo_defs+ global_flags = get_flags_from com com_opts+ (already++repo_flags) global_defs+ return $ repo_flags ++ global_flags++get_flags_from :: String -> [DarcsOption] -> [DarcsFlag] -> [(String,String,String)] -> [DarcsFlag]+get_flags_from com com_opts already defs =+ options_for com_defs com_opts com_opts +++ options_for all_defs com_opts all_opts+ where com_defs = filter (\ (c,_,_) -> c == com) defs+ all_defs = filter (\ (c,_,_) -> c == "ALL") defs+ options_for d o ao = concatMap (find_option o ao already) d+ all_opts = concatMap get_opts command_control_list+ get_opts (Command_data c) = let (o1, o2) = command_alloptions c+ in o1 ++ o2+ get_opts _ = []++find_option :: [DarcsOption] -> [DarcsOption] -> [DarcsFlag] -> (String,String,String) -> [DarcsFlag]+find_option opts all_opts already (c, f, d) =+ if null $ mapMaybe choose_option all_opts+ then error $ "Bad default option: command '"++c++"' has no option '"++f++"'."+ else concat $ mapMaybe choose_option opts+ where choose_option (DarcsNoArgOption _ fls o _)+ | o `elem` already = Just []+ | f `elem` fls = if null d+ then Just [o]+ else error $ "Bad default option: '"++f+ ++"' takes no argument, but '"++d+ ++"' argument given."+ choose_option (DarcsArgOption _ fls o _ _)+ | o `isin` already = Just []+ | f `elem` fls = if null d+ then error $ "Bad default option: '"++f+ ++"' requires an argument, but no "+ ++"argument given."+ else Just [o d]+ choose_option (DarcsMultipleChoiceOption os)+ | os `arein` already = Just []+ | otherwise = listToMaybe $ mapMaybe choose_option os+ choose_option _ = Nothing++default_content :: IO [String] -> IO [(String,String,String)]+default_content = fmap (catMaybes . map (doline.words))+ where doline (c:a:r) = Just (c, drop_dashdash a, unwords r)+ doline _ = Nothing+ drop_dashdash ('-':'-':a) = a+ drop_dashdash a = a++\end{code}+
@@ -0,0 +1,1618 @@+% Copyright (C) 2002-2004 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\begin{code}+{-# OPTIONS_GHC -cpp #-}+{-# LANGUAGE CPP #-}++#include "gadts.h"++module Darcs.Arguments ( DarcsFlag( .. ), flagToString,+ maxCount,+ isin, arein,+ definePatches, defineChanges,+ fixFilePathOrStd, fixUrl,+ fixSubPaths, areFileArgs,+ DarcsOption( .. ), option_from_darcsoption,+ help, list_options, list_files,+ any_verbosity, disable, restrict_paths,+ notest, test, working_repo_dir,+ testByDefault,+ remote_repo,+ leave_test_dir,+ possibly_remote_repo_dir, get_repourl,+ list_registered_files, list_unregistered_files,+ author, get_author, get_easy_author, get_sendmail_cmd,+ patchname_option, distname_option,+ logfile, rmlogfile, from_opt, subject, get_subject,+ in_reply_to, get_in_reply_to,+ target, cc, get_cc, output, output_auto_name,+ recursive, inventory_choices, get_inventory_choices,+ askdeps, ignoretimes, lookforadds,+ ask_long_comment, sendmail_cmd,+ environmentHelpSendmail,+ sign, verify, edit_description,+ reponame, tagname, creatorhash,+ apply_conflict_options, reply,+ pull_conflict_options, use_external_merge,+ deps_sel, nocompress,+ uncompress_nocompress, repo_combinator,+ options_latex, reorder_patches,+ noskip_boring, allow_problematic_filenames,+ applyas, human_readable,+ changes_reverse, only_to_files,+ changes_format, match_one_context, match_one_nontag,+ match_maxcount,+ send_to_context,+ get_context,+ pipe_interactive, all_interactive,+ all_pipe_interactive,+ summary, unified, tokens,+ checkpoint, partial, partial_check,+ diff_cmd_flag, diffflags, unidiff, xmloutput,+ force_replace, dry_run, dry_run_noxml,+ print_dry_run_message_and_exit, showFriendly,+ match_one, match_several, match_range,+ match_several_or_range, happy_forwarding,+ match_several_or_last,+ set_default,+ fancy_move_add,+ set_scripts_executable,+ sibling, flagsToSiblings, relink, relink_pristine, nolinks,+ files, directories, pending,+ posthook_cmd, posthook_prompt,+ get_posthook_cmd,+ prehook_cmd, prehook_prompt,+ get_prehook_cmd, nullFlag,+ umask_option,+ store_in_memory,+ patch_select_flag,+ network_options, no_cache,+ allow_unrelated_repos,+ check_or_repair+ ) where+import System.Console.GetOpt+import System.Directory ( doesDirectoryExist )+import Data.List ( (\\), nub )+import Data.Maybe ( fromMaybe, listToMaybe )+import System.Exit ( ExitCode(ExitSuccess), exitWith )+import Data.Maybe ( catMaybes )+import Control.Monad ( when, unless )+import Data.Char ( isDigit )+#ifndef WIN32+import Printer ( renderString )+import System.Posix.Env ( setEnv )+import Darcs.Patch ( list_touched_files )+import Progress ( beginTedious, endTedious, tediousSize, finishedOneIO )+#endif++import Darcs.Hopefully ( PatchInfoAnd, info, hopefullyM )+import Darcs.Patch ( RepoPatch, Patchy, showNicely, description, xml_summary )+import Darcs.Patch.Info ( to_xml )+import Darcs.Ordered ( FL, mapFL )+import qualified Darcs.Patch ( summary )+import Darcs.Utils ( askUser, maybeGetEnv, firstNotBlank, firstJustIO,+ withCurrentDirectory )+import Darcs.Repository.Prefs ( boring_file_filter, get_preflist, get_global )+import Darcs.URL ( is_file )+import Darcs.RepoPath ( AbsolutePath, AbsolutePathOrStd, SubPath, toFilePath,+ makeSubPathOf, simpleSubPath,+ ioAbsolute, ioAbsoluteOrStd,+ makeAbsolute, makeAbsoluteOrStd, rootDirectory )+import Darcs.Patch.MatchData ( patch_match )+import Darcs.Flags ( DarcsFlag(..), maxCount )+import Darcs.Repository ( slurp_pending, withRepository, ($-) )+import Darcs.Repository.HashedRepo ( slurp_all_but_darcs )+import Darcs.SlurpDirectory ( list_slurpy )+import Darcs.Global ( darcsdir )+import Printer ( Doc, putDocLn, text, vsep, ($$), vcat, insert_before_lastline,+ prefix )+import URL ( pipeliningEnabledByDefault )+#include "impossible.h"++data FlagContent = NoContent | AbsoluteContent AbsolutePath | AbsoluteOrStdContent AbsolutePathOrStd | StringContent String+ deriving (Eq, Show, Ord)++-- getContent is very tedious to write, but this is the only way (that+-- I know of) to guarantee that it works for all flags (which then+-- guarantees that isAnAbsolute, isa, flagToString, etc also work+-- properly)++-- | 'get_content' returns the content of a flag, if any.+-- For instance, the content of @Author \"Louis Aragon\"@ is @StringContent+-- \"Louis Aragon\"@, while the content of @Pipe@ is @NoContent@+getContent :: DarcsFlag -> FlagContent+getContent (PatchName s) = StringContent s+getContent (Output s) = AbsoluteOrStdContent s+getContent Verbose = NoContent+getContent Help = NoContent+getContent ListOptions = NoContent+getContent Test = NoContent+getContent NoTest = NoContent+getContent OnlyChangesToFiles = NoContent+getContent LeaveTestDir = NoContent+getContent NoLeaveTestDir = NoContent+getContent Timings = NoContent+getContent Debug = NoContent+getContent DebugVerbose = NoContent+getContent DebugHTTP = NoContent+getContent NormalVerbosity = NoContent+getContent Quiet = NoContent+getContent (Target s) = StringContent s+getContent (Cc s) = StringContent s+getContent (Subject s) = StringContent s+getContent (InReplyTo s) = StringContent s+getContent (SendmailCmd s) = StringContent s+getContent (Author s) = StringContent s+getContent (OnePatch s) = StringContent s+getContent (SeveralPatch s) = StringContent s+getContent (AfterPatch s) = StringContent s+getContent (UpToPatch s) = StringContent s+getContent (TagName s) = StringContent s+getContent (LastN s) = StringContent (show s)+getContent (MaxCount s) = StringContent (show s)+getContent (OneTag s) = StringContent s+getContent (AfterTag s) = StringContent s+getContent (UpToTag s) = StringContent s+getContent (Context s) = AbsoluteContent s+getContent (LogFile s) = AbsoluteContent s+getContent (OutputAutoName s) = AbsoluteContent s+getContent NumberPatches = NoContent+getContent (PatchIndexRange _ _) = NoContent -- FIXME this doesn't fit into a neat category+getContent Count = NoContent+getContent All = NoContent+getContent Recursive = NoContent+getContent NoRecursive = NoContent+getContent Reorder = NoContent+getContent RestrictPaths = NoContent+getContent DontRestrictPaths = NoContent+getContent AskDeps = NoContent+getContent NoAskDeps = NoContent+getContent RmLogFile = NoContent+getContent (DistName s) = StringContent s+getContent (CreatorHash s) = StringContent s+getContent (SignAs s) = StringContent s+getContent (SignSSL s) = StringContent s+getContent (Verify s) = AbsoluteContent s+getContent (VerifySSL s) = AbsoluteContent s+getContent IgnoreTimes = NoContent+getContent LookForAdds = NoContent+getContent NoLookForAdds = NoContent+getContent AnyOrder = NoContent+getContent Intersection = NoContent+getContent Unified = NoContent+getContent Union = NoContent+getContent Complement = NoContent+getContent Sign = NoContent+getContent NoSign = NoContent+getContent HappyForwarding = NoContent+getContent SSHControlMaster = NoContent+getContent NoSSHControlMaster = NoContent+getContent (Toks s) = StringContent s+getContent (WorkRepoDir s) = StringContent s+getContent (WorkRepoUrl s) = StringContent s+getContent (RemoteRepo s) = StringContent s+getContent (NewRepo s) = StringContent s+getContent (Reply s) = StringContent s+getContent EditDescription = NoContent+getContent NoEditDescription = NoContent+getContent EditLongComment = NoContent+getContent NoEditLongComment = NoContent+getContent PromptLongComment = NoContent+getContent AllowConflicts = NoContent+getContent MarkConflicts = NoContent+getContent NoAllowConflicts = NoContent+getContent Boring = NoContent+getContent AllowCaseOnly = NoContent+getContent AllowWindowsReserved = NoContent+getContent DontGrabDeps = NoContent+getContent DontPromptForDependencies = NoContent+getContent PromptForDependencies = NoContent+getContent Compress = NoContent+getContent NoCompress = NoContent+getContent UnCompress = NoContent+getContent MachineReadable = NoContent+getContent HumanReadable = NoContent+getContent Pipe = NoContent+getContent Interactive = NoContent+getContent Summary = NoContent+getContent NoSummary = NoContent+getContent (ApplyAs s) = StringContent s+getContent (DiffCmd s) = StringContent s+getContent (ExternalMerge s) = StringContent s+getContent (DiffFlags s) = StringContent s+getContent (OnePattern _) = NoContent -- FIXME!!!+getContent (SeveralPattern _) = NoContent -- FIXME!!!+getContent (UpToPattern _) = NoContent -- FIXME!!!+getContent (AfterPattern _) = NoContent -- FIXME!!!+getContent Reverse = NoContent+getContent CheckPoint = NoContent+getContent Partial = NoContent+getContent Complete = NoContent+getContent Lazy = NoContent+getContent Ephemeral = NoContent+getContent (FixFilePath _ _) = NoContent -- FIXME!!!+getContent XMLOutput = NoContent+getContent ForceReplace = NoContent+getContent NonApply = NoContent+getContent NonVerify = NoContent+getContent NonForce = NoContent+getContent DryRun = NoContent+getContent SetDefault = NoContent+getContent NoSetDefault = NoContent+getContent FancyMoveAdd = NoContent+getContent NoFancyMoveAdd = NoContent+getContent Disable = NoContent+getContent SetScriptsExecutable = NoContent+getContent DontSetScriptsExecutable = NoContent+getContent UseHashedInventory = NoContent+getContent UseOldFashionedInventory = NoContent+getContent UseFormat2 = NoContent+getContent PristinePlain = NoContent+getContent PristineNone = NoContent+getContent NoUpdateWorking = NoContent+getContent Relink = NoContent+getContent RelinkPristine = NoContent+getContent NoLinks = NoContent+getContent Files = NoContent+getContent NoFiles = NoContent+getContent Directories = NoContent+getContent NoDirectories = NoContent+getContent Pending = NoContent+getContent NoPending = NoContent+getContent NoPosthook = NoContent+getContent AskPosthook = NoContent+getContent (Sibling s) = AbsoluteContent s+getContent (PosthookCmd s) = StringContent s+getContent RunPosthook = NoContent+getContent NoPrehook = NoContent+getContent RunPrehook = NoContent+getContent AskPrehook = NoContent+getContent StoreInMemory = NoContent+getContent HTTPPipelining = NoContent+getContent NoHTTPPipelining = NoContent+getContent NoCache = NoContent+getContent NullFlag = NoContent+getContent (PrehookCmd s) = StringContent s+getContent (UMask s) = StringContent s+getContent AllowUnrelatedRepos = NoContent+getContent Check = NoContent+getContent Repair = NoContent++get_content :: DarcsFlag -> Maybe String+get_content f = do StringContent s <- Just $ getContent f+ return s++-- | @a `'isa'` b@ tests whether @a@ is flag @b@ with a string argument.+-- @b@ typically is a Flag constructor expecting a string+-- For example, @(Author \"Ted Hughes\") `isa` Author@ returns true.+isa :: DarcsFlag -> (String -> DarcsFlag) -> Bool+a `isa` b = case get_content a of+ Nothing -> False+ Just s -> a == b s++-- | @a `'isAnAbsolute'` b@ tests whether @a@ is flag @b@ with an absolute path argument.+-- @b@ typically is a Flag constructor expecting an absolute path argument+-- For example, @(Context contextfile) `isAnAbsolute` Context@ returns true.+isAnAbsolute :: DarcsFlag -> (AbsolutePath -> DarcsFlag) -> Bool+isAnAbsolute f x = case getContent f of+ AbsoluteContent s -> f == x s+ _ -> False++-- | @a `'isAnAbsoluteOrStd'` b@ tests whether @a@ is flag @b@ with a path argument.+-- @b@ typically is a Flag constructor expecting a path argument+-- For example, @(Output o) `isAnAbsoluteOrStd` @ returns true.+isAnAbsoluteOrStd :: DarcsFlag -> (AbsolutePathOrStd -> DarcsFlag) -> Bool+isAnAbsoluteOrStd f x = case getContent f of+ AbsoluteOrStdContent s -> f == x s+ _ -> False++isin :: (String->DarcsFlag) -> [DarcsFlag] -> Bool+f `isin` fs = any (`isa` f) fs++arein :: [DarcsOption] -> [DarcsFlag] -> Bool+(DarcsNoArgOption _ _ f _ : dos') `arein` fs+ = f `elem` fs || dos' `arein` fs+(DarcsArgOption _ _ f _ _ : dos') `arein` fs+ = f `isin` fs || dos' `arein` fs+(DarcsAbsPathOption _ _ f _ _ : dos') `arein` fs+ = any (`isAnAbsolute` f) fs || dos' `arein` fs+(DarcsAbsPathOrStdOption _ _ f _ _ : dos') `arein` fs+ = any (`isAnAbsoluteOrStd` f) fs || dos' `arein` fs+(DarcsOptAbsPathOption _ _ _ f _ _ : dos') `arein` fs+ = any (`isAnAbsolute` f) fs || dos' `arein` fs+(DarcsMultipleChoiceOption os: dos') `arein` fs+ = os `arein` fs || dos' `arein` fs+[] `arein` _ = False++-- | A type for darcs' options. The value contains the command line+-- switch(es) for the option, a help string, and a function to build a+-- @DarcsFlag@ from the command line arguments. for each constructor,+-- 'shortSwitches' represents the list of short command line switches+-- which invoke the option, longSwitches the list of long command line+-- switches, optDescr the description of the option, and argDescr the description+-- of its argument, if any. mkFlag is a function which makes a @DarcsFlag@ from+-- the arguments of the option.+data DarcsOption+ = DarcsArgOption [Char] [String] (String->DarcsFlag) String String+ -- ^ @DarcsArgOption shortSwitches longSwitches mkFlag ArgDescr OptDescr@+ -- The constructor for options with a string argument, such as+ -- @--tag@++ | DarcsAbsPathOption [Char] [String] (AbsolutePath -> DarcsFlag) String String+ -- ^ @DarcsAbsPathOption shortSwitches longSwitches mkFlag ArgDescr OptDescr@+ -- The constructor for options with an absolute path argument, such as+ -- @--sibling@++ | DarcsAbsPathOrStdOption [Char] [String] (AbsolutePathOrStd -> DarcsFlag) String String+ -- ^ @DarcsAbsPathOrStdOption shortSwitches longSwitches mkFlag ArgDescr OptDescr@+ -- The constructor for options with a path argument, such as @-o@++ | DarcsOptAbsPathOption [Char] [String] String (AbsolutePath -> DarcsFlag) String String+ -- ^ @DarcsOptAbsPathOrStdOption shortSwitches longSwitches defaultPath+ -- mkFlag ArgDescr OptDescr@ where defaultPath is a default value+ -- for the Path, as a string to be parsed as if it had been given+ -- on the command line.+ -- The constructor for options with an optional path argument, such as @-O@++ | DarcsNoArgOption [Char] [String] DarcsFlag String+ -- ^ @DarcsNoArgOption shortSwitches longSwitches mkFlag optDescr@+ -- The constructon fon options with no arguments.++ | DarcsMultipleChoiceOption [DarcsOption]+ -- ^ A constructor for grouping related options together, such as+ -- @--hashed@, @--darcs-2@ and @--old-fashioned-inventory@.++option_from_darcsoption :: AbsolutePath -> DarcsOption -> [OptDescr DarcsFlag]+option_from_darcsoption _ (DarcsNoArgOption a b c h) = [Option a b (NoArg c) h]+option_from_darcsoption _ (DarcsArgOption a b c n h) = [Option a b (ReqArg c n) h]+option_from_darcsoption wd (DarcsMultipleChoiceOption os) = concatMap (option_from_darcsoption wd) os+option_from_darcsoption wd (DarcsAbsPathOrStdOption a b c n h) = [Option a b (ReqArg (c . makeAbsoluteOrStd wd) n) h]+option_from_darcsoption wd (DarcsAbsPathOption a b c n h) = [Option a b (ReqArg (c . makeAbsolute wd) n) h]+option_from_darcsoption wd (DarcsOptAbsPathOption a b d c n h) = [Option a b (OptArg (c . makeAbsolute wd . fromMaybe d) n) h]++-- | 'concat_option' creates a DarcsMultipleChoiceOption from a list of+-- option, flattening any DarcsMultipleChoiceOption in the list.+concat_options :: [DarcsOption] -> DarcsOption+concat_options os = DarcsMultipleChoiceOption $ concatMap from_option os+ where+ from_option (DarcsMultipleChoiceOption xs) = xs+ from_option x = [x]++extract_fix_path :: [DarcsFlag] -> Maybe (AbsolutePath, AbsolutePath)+extract_fix_path [] = Nothing+extract_fix_path ((FixFilePath repo orig):_) = Just (repo, orig)+extract_fix_path (_:fs) = extract_fix_path fs++fixFilePath :: [DarcsFlag] -> FilePath -> IO AbsolutePath+fixFilePath opts f = case extract_fix_path opts of+ Nothing -> bug "Can't fix path in fixFilePath"+ Just (_,o) -> withCurrentDirectory o $ ioAbsolute f++fixFilePathOrStd :: [DarcsFlag] -> FilePath -> IO AbsolutePathOrStd+fixFilePathOrStd opts f =+ case extract_fix_path opts of+ Nothing -> bug "Can't fix path in fixFilePathOrStd"+ Just (_,o) -> withCurrentDirectory o $ ioAbsoluteOrStd f++fixUrl :: [DarcsFlag] -> String -> IO String+fixUrl opts f = if is_file f+ then toFilePath `fmap` fixFilePath opts f+ else return f++fixSubPaths :: [DarcsFlag] -> [FilePath] -> IO [SubPath]+fixSubPaths flags fs =+ withCurrentDirectory o $+ do fixedfs <- mapM fixit $ filter (not.null) fs+ let (good, bad) = partitionEither fixedfs+ unless (null bad) $+ putStrLn $ "Ignoring non-repository paths: " ++ unwords bad+ return $ nub good+ where+ (r,o) = case extract_fix_path flags of+ Just xxx -> xxx+ Nothing -> bug "Can't fix path in fixSubPaths"+ fixit p = do ap <- ioAbsolute p+ case makeSubPathOf r ap of+ Just sp -> return $ Right sp+ Nothing -> return $ maybe (Left p) Right $ simpleSubPath p++partitionEither :: [Either a b] -> ([b],[a])+partitionEither es = ( [b | Right b <- es]+ , [a | Left a <- es] )++-- as opposed to just '.'+areFileArgs :: [SubPath] -> Bool+areFileArgs rps = concatMap toFilePath rps /= ""++-- | 'list_option' is an option which lists the command's arguments+list_options :: DarcsOption+list_options = DarcsNoArgOption [] ["list-options"] ListOptions+ "simply list the command's arguments"++flagToString :: [DarcsOption] -> DarcsFlag -> Maybe String+flagToString x f = maybeHead $ catMaybes $ map f2o x+ where f2o (DarcsArgOption _ (s:_) c _ _) = do arg <- get_content f+ if c arg == f+ then return $ unwords [('-':'-':s), arg]+ else Nothing+ f2o (DarcsNoArgOption _ (s:_) f' _) | f == f' = Just ('-':'-':s)+ f2o (DarcsMultipleChoiceOption xs) = maybeHead $ catMaybes $ map f2o xs+ f2o _ = Nothing+ maybeHead (a:_) = Just a+ maybeHead [] = Nothing++reponame :: DarcsOption+tagname :: DarcsOption+deps_sel :: DarcsOption+checkpoint :: DarcsOption+partial :: DarcsOption+partial_check :: DarcsOption+tokens :: DarcsOption+working_repo_dir :: DarcsOption+possibly_remote_repo_dir :: DarcsOption+disable :: DarcsOption+restrict_paths :: DarcsOption++pipe_interactive, all_pipe_interactive, all_interactive, all_patches, interactive, pipe,+ human_readable, diffflags, allow_problematic_filenames, noskip_boring,+ ask_long_comment, match_one_nontag, changes_reverse, creatorhash,+ changes_format, match_one_context, happy_forwarding, send_to_context,+ diff_cmd_flag, store_in_memory, use_external_merge,+ pull_conflict_options, target, cc, apply_conflict_options, reply, xmloutput,+ distname_option, patchname_option, edit_description,+ output, output_auto_name, unidiff, repo_combinator,+ unified, summary, uncompress_nocompress, subject, in_reply_to,+ nocompress, match_several_or_range, match_several_or_last,+ author, askdeps, lookforadds, ignoretimes, test, notest, help, force_replace,+ allow_unrelated_repos,+ match_one, match_range, match_several, fancy_move_add, sendmail_cmd,+ logfile, rmlogfile, leave_test_dir, from_opt, set_default++ :: DarcsOption++recursive :: String -> DarcsOption++sign, applyas, verify :: DarcsOption+\end{code}++\section{Common options to darcs commands}++\begin{options}+--help+\end{options}+Every \verb|COMMAND| accepts \verb!--help! as an argument, which tells it to+provide a bit of help. Among other things, this help always provides an+accurate listing of the options available with that command, and is+guaranteed never to be out of sync with the version of darcs you actually+have installed (unlike this manual, which could be for an entirely+different version of darcs).+\begin{verbatim}+% darcs COMMAND --help+\end{verbatim}+\begin{code}+help = DarcsNoArgOption ['h'] ["help"] Help+ "shows brief description of command and its arguments"+\end{code}++\begin{options}+--disable+\end{options}+Every {\tt COMMAND} accepts the \verb!--disable! option, which can be used in+\verb!_darcs/prefs/defaults! to disable some commands in the repository. This+can be helpful if you want to protect the repository from accidental use of+advanced commands like obliterate, unpull, unrecord or amend-record.+\begin{code}+disable = DarcsNoArgOption [] ["disable"] Disable+ "disable this command"+\end{code}++\begin{options}+--verbose, --quiet, --normal-verbosity+\end{options}+Most commands also accept the \verb!--verbose! option, which tells darcs to+provide additional output. The amount of verbosity varies from command to+command. Commands that accept \verb!--verbose\verb! also accept \verb!--quiet\verb!,+which surpresses non-error output, and \verb!--normal-verbosity\verb! which can be+used to restore the default verbosity if \verb!--verbose! or \verb!--quiet! is in+the defaults file.++\begin{options}+--debug, --debug-http+\end{options}+Many commands also accept the \verb!--debug! option, which causes darcs to generate+additional output that may be useful for debugging its behavior, but which otherwise+would not be interesting. Option \verb!--debug-http! makes darcs output debugging+info for libcurl.+\begin{code}+any_verbosity :: [DarcsOption]+any_verbosity =[DarcsMultipleChoiceOption+ [DarcsNoArgOption [] ["debug"] Debug+ "give only debug output",+ DarcsNoArgOption [] ["debug-verbose"] DebugVerbose+ "give debug and verbose output",+ DarcsNoArgOption [] ["debug-http"] DebugHTTP+ "give debug output for libcurl",+ DarcsNoArgOption ['v'] ["verbose"] Verbose+ "give verbose output",+ DarcsNoArgOption ['q'] ["quiet"] Quiet+ "suppress informational output",+ DarcsNoArgOption [] ["standard-verbosity"] NormalVerbosity+ "neither verbose nor quiet output"],+ DarcsNoArgOption [] ["timings"] Timings "provide debugging timings information"]+\end{code}++\begin{options}+--repodir+\end{options}+Another common option is the \verb!--repodir! option, which allows you to+specify the directory of the repository in which to perform the command.+This option is used with commands, such as whatsnew, that ordinarily would+be performed within a repository directory, and allows you to use those+commands without actually being in the repository directory when calling the+command. This is useful when running darcs in a pipe, as might be the case+when running \verb'apply' from a mailer.++\begin{code}+working_repo_dir = DarcsArgOption [] ["repodir"] WorkRepoDir "DIRECTORY"+ "specify the repository directory in which to run"+possibly_remote_repo_dir = DarcsArgOption [] ["repo"] WorkRepoUrl "URL"+ "specify the repository URL"++-- | 'get_repourl' takes a list of flags and returns the url of the+-- repository specified by @Repodir \"directory\"@ in that list of flags, if any.+-- This flag is present if darcs was invoked with @--repodir=DIRECTORY@+get_repourl :: [DarcsFlag] -> Maybe String+get_repourl [] = Nothing+get_repourl (WorkRepoUrl d:_) | not (is_file d) = Just d+get_repourl (_:fs) = get_repourl fs+\end{code}++\begin{options}+--remote-repo+\end{options}++Some commands, such as \verb'pull' require a remote repository to be specified,+either from the command line or as a default. The \verb!--remote-repo!+provides an alternative way to supply this remote repository path. This flag+can be seen as temporarily ``replacing'' the default repository. Setting it+causes the command to ignore the default repository (it also does not affect,+i.e. overwrite the default repository). On the other hand, if any other+repositories are supplied as command line arguments, this flag will be ignored+(and the default repository may be overwritten).++\begin{code}+-- | 'remote_repo' is the option used to specify the URL of the remote+-- repository to work with+remote_repo :: DarcsOption+remote_repo = DarcsArgOption [] ["remote-repo"] RemoteRepo "URL"+ "specify the remote repository URL to work with"+\end{code}++\input{Darcs/Match.lhs}+\input{Darcs/Patch/Match.lhs}++\begin{code}+patchname_option = DarcsArgOption ['m'] ["patch-name"] PatchName "PATCHNAME"+ "name of patch"++send_to_context = DarcsAbsPathOption [] ["context"] Context "FILENAME"+ "send to context stored in FILENAME"++match_one_context =+ DarcsMultipleChoiceOption+ [DarcsArgOption [] ["to-match"] mp "PATTERN"+ "select changes up to a patch matching PATTERN",+ DarcsArgOption [] ["to-patch"] OnePatch "REGEXP"+ "select changes up to a patch matching REGEXP",+ __tag,+ DarcsAbsPathOption [] ["context"] Context "FILENAME"+ "version specified by the context in FILENAME"+ ]+ where mp s = OnePattern (patch_match s)++match_one = concat_options [__match, __patch, __tag, __index]+match_one_nontag = concat_options [__match, __patch, __index]+match_several = concat_options [__matches, __patches, __tags]+match_range = concat_options [match_to, match_from, __match, __patch, __last, __indexes]+match_several_or_range = concat_options [match_to, match_from, __last, __indexes,+ __matches, __patches, __tags]+match_several_or_last = concat_options [match_from, __last, __matches, __patches, __tags]++match_to, match_from :: DarcsOption+match_to = DarcsMultipleChoiceOption+ [DarcsArgOption [] ["to-match"] uptop "PATTERN"+ "select changes up to a patch matching PATTERN",+ DarcsArgOption [] ["to-patch"] UpToPatch "REGEXP"+ "select changes up to a patch matching REGEXP",+ DarcsArgOption [] ["to-tag"] UpToTag "REGEXP"+ "select changes up to a tag matching REGEXP"]+ where uptop s = UpToPattern (patch_match s)+match_from = DarcsMultipleChoiceOption+ [DarcsArgOption [] ["from-match"] fromp "PATTERN"+ "select changes starting with a patch matching PATTERN",+ DarcsArgOption [] ["from-patch"] AfterPatch "REGEXP"+ "select changes starting with a patch matching REGEXP",+ DarcsArgOption [] ["from-tag"] AfterTag "REGEXP"+ "select changes starting with a tag matching REGEXP"]+ where fromp s = AfterPattern (patch_match s)++__tag, __tags, __patch, __patches, __match, __matches, __last, __index, __indexes :: DarcsOption++__tag = DarcsArgOption ['t'] ["tag"] OneTag "REGEXP"+ "select tag matching REGEXP"+__tags = DarcsArgOption ['t'] ["tags"] OneTag "REGEXP"+ "select tags matching REGEXP"++__patch = DarcsArgOption ['p'] ["patch"] OnePatch "REGEXP"+ "select a single patch matching REGEXP"+__patches = DarcsArgOption ['p'] ["patches"] SeveralPatch "REGEXP"+ "select patches matching REGEXP"++__match = DarcsArgOption [] ["match"] mp "PATTERN"+ "select a single patch matching PATTERN"+ where mp s = OnePattern (patch_match s)+__matches = DarcsArgOption [] ["matches"] mp "PATTERN"+ "select patches matching PATTERN"+ where mp s = SeveralPattern (patch_match s)++__last = DarcsArgOption [] ["last"] lastn "NUMBER"+ "select the last NUMBER patches"+ where lastn = LastN . number_string++__index = DarcsArgOption ['n'] ["index"] indexrange "N" "select one patch"+ where indexrange s = if all isDigit s+ then PatchIndexRange (read s) (read s)+ else PatchIndexRange 0 0++__indexes = DarcsArgOption ['n'] ["index"] indexrange "N-M" "select a range of patches"+ where indexrange s = if all isokay s+ then if '-' `elem` s+ then let x1 = takeWhile (/= '-') s+ x2 = reverse $ takeWhile (/= '-') $ reverse s+ in PatchIndexRange (read x1) (read x2)+ else PatchIndexRange (read s) (read s)+ else PatchIndexRange 0 0+ isokay c = isDigit c || c == '-'++match_maxcount :: DarcsOption+match_maxcount = DarcsArgOption [] ["max-count"] mc "NUMBER"+ "return only NUMBER results"+ where mc = MaxCount . number_string+++-- | 'get_context' takes a list of flags and returns the context+-- specified by @Context c@ in that list of flags, if any.+-- This flag is present if darcs was invoked with @--context=FILE@+get_context :: [DarcsFlag] -> Maybe AbsolutePath+get_context xs = listToMaybe [ c | Context c <- xs ]++notest = DarcsMultipleChoiceOption+ [DarcsNoArgOption [] ["no-test"] NoTest "don't run the test script",+ DarcsNoArgOption [] ["test"] Test "run the test script"]+test = DarcsMultipleChoiceOption+ [DarcsNoArgOption [] ["test"] Test "run the test script",+ DarcsNoArgOption [] ["no-test"] NoTest "don't run the test script"]+leave_test_dir = DarcsMultipleChoiceOption+ [DarcsNoArgOption [] ["leave-test-directory"]+ LeaveTestDir "don't remove the test directory",+ DarcsNoArgOption [] ["remove-test-directory"]+ NoLeaveTestDir "remove the test directory"]++testByDefault :: [DarcsFlag] -> [DarcsFlag]+testByDefault o = if NoTest `elem` o then o else Test:o+\end{code}++\begin{options}+--ignore-times+\end{options}+Darcs optimizes its operations by keeping track of the modification times+of your files. This dramatically speeds up commands such as+\verb!whatsnew! and \verb!record! which would otherwise require reading+every file in the repository and comparing it with a reference version. However,+there are times when this can cause problems, such as when running a series+of darcs commands from a script, in which case often a file will be+modified twice in the same second, which can lead to the second+modification going unnoticed. The solution to such predicaments is the+\verb!--ignore-times! option, which instructs darcs not to trust the file+modification times, but instead to check each file's contents explicitly.+\begin{code}+ignoretimes = DarcsNoArgOption [] ["ignore-times"] IgnoreTimes+ "don't trust the file modification times"+lookforadds =+ DarcsMultipleChoiceOption+ [DarcsNoArgOption ['l'] ["look-for-adds"] LookForAdds+ "look for (non-boring) files that could be added",+ DarcsNoArgOption [] ["dont-look-for-adds"] NoLookForAdds+ "don't look for any files that could be added [DEFAULT]"]++fancy_move_add =+ DarcsMultipleChoiceOption+ [DarcsNoArgOption [] ["date-trick"] FancyMoveAdd+ "add files with date appended to avoid conflict [EXPERIMENTAL] ",+ DarcsNoArgOption [] ["no-date-trick"] NoFancyMoveAdd+ "don't use experimental date appending trick [DEFAULT]"]++askdeps =+ DarcsMultipleChoiceOption+ [DarcsNoArgOption [] ["ask-deps"] AskDeps+ "ask for extra dependencies",+ DarcsNoArgOption [] ["no-ask-deps"] NoAskDeps+ "don't ask for extra dependencies"]++ask_long_comment =+ DarcsMultipleChoiceOption+ [DarcsNoArgOption [] ["edit-long-comment"] EditLongComment+ "edit the long comment by default",+ DarcsNoArgOption [] ["skip-long-comment"] NoEditLongComment+ "don't give a long comment",+ DarcsNoArgOption [] ["prompt-long-comment"] PromptLongComment+ "prompt for whether to edit the long comment"]+\end{code}++\begin{options}+--author+\end{options}+\label{env:DARCS_EMAIL}+Several commands need to be able to identify you. Conventionally, you+provide an email address for this purpose, which can include comments,+e.g.\ \verb!David Roundy <droundy@abridgegame.org>!. The easiest way to do+this is+to define an environment variable \verb!EMAIL! or \verb!DARCS_EMAIL! (with+the latter overriding the former). You can also override this using the+\verb!--author! flag to any command. Alternatively, you could set your+email address on a per-repository basis using the ``defaults'' mechanism+for ``ALL'' commands, as described in Appendix~\ref{repository_format}.+Or, you could specify the author on a per-repository basis using the+\verb!_darcs/prefs/author! file as described in section~\ref{author_prefs}.++Also, a global author file can be created in your home directory with the name+\verb!.darcs/author!, on MS Windows~\ref{ms_win}. This file overrides the+contents of the environment variables, but a repository-specific author+file overrides the global author file.++\begin{code}+logfile = DarcsAbsPathOption [] ["logfile"] LogFile "FILE"+ "give patch name and comment in file"++rmlogfile = DarcsNoArgOption [] ["delete-logfile"] RmLogFile+ "delete the logfile when done"++author = DarcsArgOption ['A'] ["author"] Author "EMAIL" "specify author id"+from_opt = DarcsArgOption [] ["from"] Author "EMAIL" "specify email address"++-- | 'get_author' takes a list of flags and returns the author of the+-- change specified by @Author \"Leo Tolstoy\"@ in that list of flags, if any.+-- Otherwise, if @Pipe@ is present, asks the user who is the author and+-- returns the answer. If neither are present, try to guess the author,+-- from @_darcs/prefs@, and if it's not possible, ask the user.+get_author :: [DarcsFlag] -> IO String+get_author (Author a:_) = return a+get_author (Pipe:_) = do askUser "Who is the author? "+get_author (_:flags) = get_author flags+get_author [] = do+ easy_author <- get_easy_author+ case easy_author of+ Just a -> return a+ Nothing -> do+ aminrepo <- doesDirectoryExist (darcsdir++"/prefs")+ if aminrepo then do+ putDocLn $+ text "Each patch is attributed to its author, usually by email address (for" $$+ text "example, `Fred Bloggs <fred@example.net>'). Darcs could not determine" $$+ text "your email address, so you will be prompted for it." $$+ text "" $$+ text ("Your address will be stored in " ++ darcsdir ++ "/prefs/author.") $$+ text "It will be used for all patches recorded in this repository." $$+ text "If you move that file to ~/.darcs/author, it will be used for patches" $$+ text "you record in ALL repositories."+ add <- askUser "What is your email address? "+ writeFile (darcsdir++"/prefs/author") add+ return add+ else askUser "What is your email address (e.g. Fred Bloggs <fred@example.net>)? "++-- | 'get_easy_author' tries to get the author name first from the repository preferences,+-- then from global preferences, then from environment variables. Returns 'Nothing' if it+-- could not get it.+get_easy_author :: IO (Maybe String)+get_easy_author = firstJustIO [ firstNotBlank `fmap` get_preflist "author",+ firstNotBlank `fmap` get_global "author",+ maybeGetEnv "DARCS_EMAIL",+ maybeGetEnv "EMAIL" ]+\end{code}++\begin{options}+--dont-compress, --compress+\end{options}+By default, darcs commands that write patches to disk will compress the+patch files. If you don't want this, you can choose the+\verb!--dont-compress! option, which causes darcs not to compress the patch+file.++\begin{code}+nocompress = concat_options [__compress, __dont_compress]+uncompress_nocompress = concat_options [__compress, __dont_compress, __uncompress]++__compress, __dont_compress, __uncompress :: DarcsOption+__compress = DarcsNoArgOption [] ["compress"] Compress+ "create compressed patches"+__dont_compress = DarcsNoArgOption [] ["dont-compress"] NoCompress+ "don't create compressed patches"+__uncompress = DarcsNoArgOption [] ["uncompress"] UnCompress+ "uncompress patches"++summary = DarcsMultipleChoiceOption+ [DarcsNoArgOption ['s'] ["summary"] Summary "summarize changes",+ DarcsNoArgOption [] ["no-summary"] NoSummary "don't summarize changes"]+unified = DarcsNoArgOption ['u'] ["unified"] Unified+ "output patch in a darcs-specific format similar to diff -u"+unidiff = DarcsNoArgOption ['u'] ["unified"] Unified+ "pass -u option to diff"+diff_cmd_flag = DarcsArgOption [] ["diff-command"]+ DiffCmd "COMMAND" "specify diff command (ignores --diff-opts)"+store_in_memory = DarcsNoArgOption [] ["store-in-memory"] StoreInMemory+ "do patch application in memory rather than on disk"++target = DarcsArgOption [] ["to"] Target "EMAIL" "specify destination email"+cc = DarcsArgOption [] ["cc"] Cc "EMAIL" "mail results to additional EMAIL(s). Requires --reply"++-- |'get_cc' takes a list of flags and returns the addresses to send a copy of+-- the patch bundle to when using @darcs send@.+-- looks for a cc address specified by @Cc \"address\"@ in that list of flags.+-- Returns the addresses as a comma separated string.+get_cc :: [DarcsFlag] -> String+get_cc fs = lt $ catMaybes $ map whatcc fs+ where whatcc (Cc t) = Just t+ whatcc _ = Nothing+ lt [t] = t+ lt [t,""] = t+ lt (t:ts) = t++" , "++lt ts+ lt [] = ""++subject = DarcsArgOption [] ["subject"] Subject "SUBJECT" "specify mail subject"++-- |'get_subject' takes a list of flags and returns the subject of the mail+-- to be sent by @darcs send@. Looks for a subject specified by+-- @Subject \"subject\"@ in that list of flags, if any.+-- This flag is present if darcs was invoked with @--subject=SUBJECT@+get_subject :: [DarcsFlag] -> Maybe String+get_subject (Subject s:_) = Just s+get_subject (_:fs) = get_subject fs+get_subject [] = Nothing++in_reply_to = DarcsArgOption [] ["in-reply-to"] InReplyTo "EMAIL" "specify in-reply-to header"+get_in_reply_to :: [DarcsFlag] -> Maybe String+get_in_reply_to (InReplyTo s:_) = Just s+get_in_reply_to (_:fs) = get_in_reply_to fs+get_in_reply_to [] = Nothing++output = DarcsAbsPathOrStdOption ['o'] ["output"] Output "FILE"+ "specify output filename"++output_auto_name = DarcsOptAbsPathOption ['O'] ["output-auto-name"] "." OutputAutoName "DIRECTORY"+ "output to automatically named file in DIRECTORY, default: current directory"++edit_description =+ DarcsMultipleChoiceOption+ [DarcsNoArgOption [] ["edit-description"] EditDescription+ "edit the patch bundle description",+ DarcsNoArgOption [] ["dont-edit-description"] NoEditDescription+ "don't edit the patch bundle description"]++distname_option = DarcsArgOption ['d'] ["dist-name"] DistName "DISTNAME"+ "name of version"++recursive h+ = DarcsMultipleChoiceOption+ [DarcsNoArgOption ['r'] ["recursive"] Recursive h,+ DarcsNoArgOption [] ["not-recursive"] NoRecursive ("don't "++h)]++inventory_choices :: DarcsOption+inventory_choices =+ DarcsMultipleChoiceOption+ [DarcsNoArgOption [] ["hashed"] UseHashedInventory+ "Some new features. Compatible with older repos",+ DarcsNoArgOption [] ["darcs-2"] UseFormat2+ "All features. Related repos must use same format [DEFAULT]",+ DarcsNoArgOption [] ["old-fashioned-inventory"] UseOldFashionedInventory+ "Minimal features. What older repos use."]++get_inventory_choices :: DarcsOption+get_inventory_choices =+ DarcsMultipleChoiceOption+ [DarcsNoArgOption [] ["hashed"] UseHashedInventory+ "Convert darcs-1 format to hashed format",+ DarcsNoArgOption [] ["old-fashioned-inventory"] UseOldFashionedInventory+ "Convert from hashed to darcs-1 format"]++xmloutput = DarcsNoArgOption [] ["xml-output"] XMLOutput+ "generate XML formatted output"++creatorhash = DarcsArgOption [] ["creator-hash"] CreatorHash "HASH"+ "specify hash of creator patch (see docs)"++sign = DarcsMultipleChoiceOption+ [DarcsNoArgOption [] ["sign"] Sign+ "sign the patch with your gpg key",+ DarcsArgOption [] ["sign-as"] SignAs "KEYID"+ "sign the patch with a given keyid",+ DarcsArgOption [] ["sign-ssl"] SignSSL "IDFILE"+ "sign the patch using openssl with a given private key",+ DarcsNoArgOption [] ["dont-sign"] NoSign+ "don't sign the patch"]+applyas = DarcsMultipleChoiceOption+ [DarcsArgOption [] ["apply-as"] ApplyAs "USERNAME"+ "apply patch as another user using sudo",+ DarcsNoArgOption [] ["apply-as-myself"] NonApply+ "don't use sudo to apply as another user [DEFAULT]"]+happy_forwarding = DarcsNoArgOption [] ["happy-forwarding"] HappyForwarding+ "forward unsigned messages without extra header"+set_default = DarcsMultipleChoiceOption+ [DarcsNoArgOption [] ["set-default"] SetDefault+ "set default repository [DEFAULT]",+ DarcsNoArgOption [] ["no-set-default"] NoSetDefault+ "don't set default repository"]++verify = DarcsMultipleChoiceOption+ [DarcsAbsPathOption [] ["verify"] Verify "PUBRING"+ "verify that the patch was signed by a key in PUBRING",+ DarcsAbsPathOption [] ["verify-ssl"] VerifySSL "KEYS"+ "verify using openSSL with authorized keys from file KEYS",+ DarcsNoArgOption [] ["no-verify"] NonVerify+ "don't verify patch signature"]++reponame = DarcsArgOption [] ["repo-name","repodir"] NewRepo "DIRECTORY"+ "path of output directory" --repodir is there for compatibility+ --should be removed eventually+tagname = DarcsArgOption ['t'] ["tag"] TagName "TAGNAME"+ "name of version to checkpoint"+deps_sel = DarcsMultipleChoiceOption+ [DarcsNoArgOption [] ["no-deps"] DontGrabDeps+ "don't automatically fulfill dependencies",+ DarcsNoArgOption [] ["dont-prompt-for-dependencies"] DontPromptForDependencies+ "don't ask about patches that are depended on by matched patches (with --match or --patch)",+ DarcsNoArgOption [] ["prompt-for-dependencies"] PromptForDependencies+ "prompt about patches that are depended on by matched patches [DEFAULT]"]+checkpoint = DarcsNoArgOption [] ["checkpoint"] CheckPoint+ "create a checkpoint file (see get --partial)"+tokens = DarcsArgOption [] ["token-chars"] Toks "\"[CHARS]\""+ "define token to contain these characters"++partial = concat_options [__partial, __lazy, __ephemeral, __complete]+partial_check = concat_options [__complete, __partial]++__partial, __lazy, __ephemeral, __complete :: DarcsOption+__partial = DarcsNoArgOption [] ["partial"] Partial+ "get partial repository using checkpoint (old-fashioned format only)"+__lazy = DarcsNoArgOption [] ["lazy"] Lazy+ "get patch files only as needed"+__ephemeral = DarcsNoArgOption [] ["ephemeral"] Ephemeral+ "don't save patch files in the repository"+__complete = DarcsNoArgOption [] ["complete"] Complete+ "get a complete copy of the repository"++force_replace = DarcsMultipleChoiceOption+ [DarcsNoArgOption ['f'] ["force"] ForceReplace+ "proceed with replace even if 'new' token already exists",+ DarcsNoArgOption [] ["no-force"]+ NonForce "don't force the replace if it looks scary"]++reply = DarcsArgOption [] ["reply"] Reply "FROM" "reply to email-based patch using FROM address"+apply_conflict_options+ = DarcsMultipleChoiceOption+ [DarcsNoArgOption [] ["mark-conflicts"]+ MarkConflicts "mark conflicts",+ DarcsNoArgOption [] ["allow-conflicts"]+ AllowConflicts "allow conflicts, but don't mark them",+ DarcsNoArgOption [] ["no-resolve-conflicts"] NoAllowConflicts+ "equivalent to --dont-allow-conflicts, for backwards compatibility",+ DarcsNoArgOption [] ["dont-allow-conflicts"]+ NoAllowConflicts "fail on patches that create conflicts [DEFAULT]"]+pull_conflict_options+ = DarcsMultipleChoiceOption+ [DarcsNoArgOption [] ["mark-conflicts"]+ MarkConflicts "mark conflicts [DEFAULT]",+ DarcsNoArgOption [] ["allow-conflicts"]+ AllowConflicts "allow conflicts, but don't mark them",+ DarcsNoArgOption [] ["dont-allow-conflicts"]+ NoAllowConflicts "fail on patches that create conflicts"]+use_external_merge = DarcsArgOption [] ["external-merge"]+ ExternalMerge "COMMAND" "use external tool to merge conflicts"+\end{code}++\begin{options}+--dry-run+\end{options}+The \verb!--dry-run! option will cause darcs not to actually take the specified+action, but only print what would have happened. Not all commands accept+\verb!--dry-run!, but those that do should accept the \verb!--summary! option.++\begin{options}+--summary, --no-summary+\end{options}+The \verb!--summary! option shows a summary of the patches that would have been+pulled/pushed/whatever. The format is similar to the output format of+\verb!cvs update! and looks like this:++\begin{verbatim}+A ./added_but_not_recorded.c+A! ./added_but_not_recorded_conflicts.c+a ./would_be_added_if_look_for_adds_option_was_used.h++M ./modified.t -1 +1+M! ./modified_conflicts.t -1 +1++R ./removed_but_not_recorded.c+R! ./removed_but_not_recorded_conflicts.c++\end{verbatim}++You can probably guess what the flags mean from the clever file names.+\begin{description}+\item{\texttt{A}} is for files that have been added but not recorded yet.+\item{\texttt{a}} is for files found using the \verb!--look-for-adds! option available for+\verb!whatsnew! and \verb!record!. They have not been added yet, but would be+added automatically if \verb!--look-for-adds! were used with the next+\verb!record! command.++\item{\texttt{M}} is for files that have been modified in the working directory but not+recorded yet. The number of added and subtracted lines is also shown.++\item{\texttt{R}} is for files that have been removed, but the removal is not+recorded yet.+\end{description}+An exclamation mark appears next to any option that has a conflict.++\begin{code}+-- NOTE: I'd rather work to have no uses of dry_run_noxml, so that any time+-- --dry-run is a possibility, automated users can examine the results more+-- easily with --xml.+dry_run_noxml :: DarcsOption+dry_run_noxml = DarcsNoArgOption [] ["dry-run"] DryRun+ "don't actually take the action"++dry_run :: [DarcsOption]+dry_run = [dry_run_noxml, xmloutput]++-- | @'showFriendly' flags patch@ returns a 'Doc' representing the right+-- way to show @patch@ given the list @flags@ of flags darcs was invoked with.+showFriendly :: Patchy p => [DarcsFlag] -> p C(x y) -> Doc+showFriendly opts p | Verbose `elem` opts = showNicely p+ | Summary `elem` opts = Darcs.Patch.summary p+ | otherwise = description p++-- | @'print_dry_run_message_and_exit' action opts patches@ prints a string+-- representing the action that would be taken if the @--dry-run@ option+-- had not been passed to darcs. Then darcs exits successfully.+-- @action@ is the name of the action being taken, like @\"push\"@+-- @opts@ is the list of flags which were sent to darcs+-- @patches@ is the sequence of patches which would be touched by @action@.+print_dry_run_message_and_exit :: RepoPatch p => String -> [DarcsFlag] -> FL (PatchInfoAnd p) C(x y) -> IO ()+print_dry_run_message_and_exit action opts patches =+ do when (DryRun `elem` opts) $ do+ putInfo $ text $ "Would " ++ action ++ " the following changes:"+ putDocLn $ put_mode+ putInfo $ text $ ""+ putInfo $ text $ "Making no changes: this is a dry run."+ exitWith ExitSuccess+ when (All `elem` opts && Summary `elem` opts) $ do+ putInfo $ text $ "Will " ++ action ++ " the following changes:"+ putDocLn $ put_mode+ where put_mode = if XMLOutput `elem` opts+ then (text "<patches>" $$+ vcat (mapFL (indent . xml_info) patches) $$+ text "</patches>")+ else (vsep $ mapFL (showFriendly opts) patches)+ putInfo = if XMLOutput `elem` opts then \_ -> return () else putDocLn+ xml_info pl+ | Summary `elem` opts = xml_with_summary pl+ | otherwise = (to_xml . info) pl+ + xml_with_summary hp+ | Just p <- hopefullyM hp = insert_before_lastline+ (to_xml $ info hp) (indent $ xml_summary p)+ xml_with_summary hp = to_xml (info hp)+ indent = prefix " "+++\end{code}++\input{Darcs/Resolution.lhs}++\begin{code}+noskip_boring = DarcsNoArgOption [] ["boring"]+ Boring "don't skip boring files"+allow_problematic_filenames = DarcsMultipleChoiceOption+ [DarcsNoArgOption [] ["case-ok"] AllowCaseOnly+ "don't refuse to add files differing only in case"+ ,DarcsNoArgOption [] ["reserved-ok"] AllowWindowsReserved+ "don't refuse to add files with Windows-reserved names"+ ]+diffflags = DarcsArgOption [] ["diff-opts"]+ DiffFlags "OPTIONS" "options to pass to diff"++changes_format = DarcsMultipleChoiceOption+ [DarcsNoArgOption [] ["context"]+ (Context rootDirectory) "give output suitable for get --context",+ xmloutput,+ human_readable,+ DarcsNoArgOption [] ["number"] NumberPatches "number the changes",+ DarcsNoArgOption [] ["count"] Count "output count of changes"+ ]+changes_reverse = DarcsNoArgOption [] ["reverse"] Reverse+ "show changes in reverse order"++only_to_files :: DarcsOption+only_to_files = DarcsNoArgOption [] ["only-to-files"] OnlyChangesToFiles+ "show only changes to specified files"++human_readable = DarcsNoArgOption [] ["human-readable"]+ HumanReadable "give human-readable output"+pipe = DarcsNoArgOption [] ["pipe"] Pipe "ask user interactively for the patch metadata"++interactive =+ DarcsNoArgOption ['i'] ["interactive"] Interactive+ "prompt user interactively"+all_patches = DarcsNoArgOption ['a'] ["all"] All "answer yes to all patches"++all_interactive = DarcsMultipleChoiceOption [all_patches, interactive]++all_pipe_interactive+ = DarcsMultipleChoiceOption [all_patches,pipe,interactive]++pipe_interactive =+ DarcsMultipleChoiceOption [pipe, interactive]++repo_combinator =+ DarcsMultipleChoiceOption+ [DarcsNoArgOption [] ["intersection"] Intersection+ "take intersection of all repositories",+ DarcsNoArgOption [] ["union"] Union+ "take union of all repositories [DEFAULT]",+ DarcsNoArgOption [] ["complement"] Complement+ "take complement of repositories (in order listed)"]++-- | 'list_files' returns the list of all non-boring files in the repository+list_files :: IO [String]+list_files = do s <- slurp_all_but_darcs "."+ skip_boring <- boring_file_filter+ return (map drop_dotslash $ skip_boring $ list_slurpy s)++drop_dotslash :: String -> String+drop_dotslash ('.':'/':x) = drop_dotslash x+drop_dotslash x = x++-- | 'list_unregistered_files' returns the list of all non-boring unregistered+-- files in the repository.+list_unregistered_files :: IO [String]+list_unregistered_files = withRepository [] $- \repository ->+ do s <- slurp_all_but_darcs "."+ skip_boring <- boring_file_filter+ regs <- slurp_pending repository+ return $ map drop_dotslash $ (skip_boring $ list_slurpy s) \\ (list_slurpy regs)++-- | 'list_registered_files' returns the list of all registered files in the repository.+list_registered_files :: IO [String]+list_registered_files =+ (map drop_dotslash . list_slurpy) `fmap` (withRepository [] slurp_pending)++options_latex :: [DarcsOption] -> String+options_latex opts = "\\begin{tabular}{lll}\n"+++ unlines (map option_latex opts)+++ "\\end{tabular}\n"++latex_help :: String -> String+latex_help h+ = "\\begin{minipage}{7cm}\n\\raggedright\n" ++ h ++ "\\end{minipage}\n"++option_latex :: DarcsOption -> String+option_latex (DarcsNoArgOption a b _ h) =+ show_short_options a ++ show_long_options b ++ latex_help h ++ "\\\\"+option_latex (DarcsArgOption a b _ arg h) =+ show_short_options a +++ show_long_options (map (++(" "++arg)) b) ++ latex_help h ++ "\\\\"+option_latex (DarcsAbsPathOrStdOption a b _ arg h) =+ show_short_options a +++ show_long_options (map (++(" "++arg)) b) ++ latex_help h ++ "\\\\"+option_latex (DarcsAbsPathOption a b _ arg h) =+ show_short_options a +++ show_long_options (map (++(" "++arg)) b) ++ latex_help h ++ "\\\\"+option_latex (DarcsOptAbsPathOption a b _ _ arg h) =+ show_short_options a +++ show_long_options (map (++("[="++arg++"]")) b) ++ latex_help h ++ "\\\\"+option_latex (DarcsMultipleChoiceOption os) =+ unlines (map option_latex os)++show_short_options :: [Char] -> String+show_short_options [] = "&"+show_short_options [c] = "\\verb!-"++[c]++"! &"+show_short_options (c:cs) = "\\verb!-"++[c]++"!,"++show_short_options cs++show_long_options :: [String] -> String+show_long_options [] = " &"+show_long_options [s] = "\\verb!--" ++ s ++ "! &"+show_long_options (s:ss)+ = "\\verb!--" ++ s ++ "!,"++ show_long_options ss++set_scripts_executable :: DarcsOption+set_scripts_executable = DarcsMultipleChoiceOption+ [DarcsNoArgOption [] ["set-scripts-executable"] SetScriptsExecutable+ "make scripts executable",+ DarcsNoArgOption [] ["dont-set-scripts-executable"] DontSetScriptsExecutable+ "don't make scripts executable"]++relink, relink_pristine, sibling :: DarcsOption+relink = DarcsNoArgOption [] ["relink"] Relink+ "relink random internal data to a sibling"++relink_pristine = DarcsNoArgOption [] ["relink-pristine"] RelinkPristine+ "relink pristine tree (not recommended)"++sibling = DarcsAbsPathOption [] ["sibling"] Sibling "URL"+ "specify a sibling directory"++-- | 'flagsToSiblings' collects the contents of all @Sibling@ flags in a list of flags.+flagsToSiblings :: [DarcsFlag] -> [AbsolutePath]+flagsToSiblings ((Sibling s) : l) = s : (flagsToSiblings l)+flagsToSiblings (_ : l) = flagsToSiblings l+flagsToSiblings [] = []++nolinks :: DarcsOption+nolinks = DarcsNoArgOption [] ["nolinks"] NoLinks+ "do not link repository or pristine to sibling"++reorder_patches :: DarcsOption+reorder_patches = DarcsNoArgOption [] ["reorder-patches"] Reorder+ "reorder the patches in the repository"+\end{code}+\begin{options}+--sendmail-command+\end{options}+\darcsEnv{SENDMAIL}++\begin{code}+sendmail_cmd = DarcsArgOption [] ["sendmail-command"] SendmailCmd "COMMAND" "specify sendmail command"++environmentHelpSendmail :: ([String], [String])+environmentHelpSendmail = (["SENDMAIL"], [+ "On Unix, the `darcs send' command relies on sendmail(8). The",+ "`--sendmail-command' or $SENDMAIL environment variable can be used to",+ "provide an explicit path to this program; otherwise the standard",+ "locations /usr/sbin/sendmail and /usr/lib/sendmail will be tried."])+-- FIXME: mention the following also:+-- * sendmail(8) is not sendmail-specific;+-- * nowadays, desktops often have no MTA or an unconfigured MTA --+-- which is awful, because it accepts mail but doesn't relay it;+-- * in this case, can be a sendmail(8)-emulating wrapper on top of an+-- MUA that sends mail directly to a smarthost; and+-- * on a multi-user system without an MTA and on which you haven't+-- got root, can be msmtp.++-- |'get_sendmail_cmd' takes a list of flags and returns the sendmail command+-- to be used by @darcs send@. Looks for a command specified by+-- @SendmailCmd \"command\"@ in that list of flags, if any.+-- This flag is present if darcs was invoked with @--sendmail-command=COMMAND@+-- Alternatively the user can set @$S@@ENDMAIL@ which will be used as a fallback if present.+get_sendmail_cmd :: [DarcsFlag] -> IO String +get_sendmail_cmd (SendmailCmd a:_) = return a+get_sendmail_cmd (_:flags) = get_sendmail_cmd flags+get_sendmail_cmd [] = do easy_sendmail <- firstJustIO [ maybeGetEnv "SENDMAIL" ]+ case easy_sendmail of+ Just a -> return a+ Nothing -> return ""++files :: DarcsOption+files = DarcsMultipleChoiceOption+ [DarcsNoArgOption [] ["files"] Files+ "include files in output [DEFAULT]",+ DarcsNoArgOption [] ["no-files"] NoFiles+ "don't include files in output"]++directories :: DarcsOption+directories = DarcsMultipleChoiceOption+ [DarcsNoArgOption [] ["directories"] Directories+ "include directories in output [DEFAULT]",+ DarcsNoArgOption [] ["no-directories"] NoDirectories+ "don't include directories in output"]++pending :: DarcsOption+pending = DarcsMultipleChoiceOption+ [DarcsNoArgOption [] ["pending"] Pending+ "reflect pending patches in output [DEFAULT]",+ DarcsNoArgOption [] ["no-pending"] NoPending+ "only included recorded patches in output"]++nullFlag :: DarcsOption -- "null" is already taken+nullFlag = DarcsNoArgOption ['0'] ["null"] NullFlag+ "separate file names by NUL characters"+\end{code}+\begin{options}+--posthook=COMMAND, --no-posthook+\end{options}+To provide a command that should be run whenever a darcs command completes+successfully, use \verb!--posthook! to specify the command. This is useful+for people who want to have a command run whenever a patch is applied. Using+\verb!--no-posthook! will disable running the command.+\begin{options}+--run-posthook, --prompt-posthook+\end{options}+These options control prompting before running the posthook. Use+\verb!--prompt-posthook! to have darcs prompt before running the+posthook command. You may use --run-posthook to reenable the default+behavior of running user-specified posthooks.++Some darcs commands export to the posthook command information about the+changes being made. In particular, three environment variables are defined.+\verb!DARCS_PATCHES! contains a human-readable summary of the patches being+acted upon. The format is the same as "darcs changes". \verb!DARCS_PATCHES_XML!+Contains the same details, in the same XML format as "darcs changes". Finally,+\verb!DARCS_FILES! contains a list of the files affected, one file per line.+If your repository has filenames including newlines, you'll just have to+cope. Note, however, that \emph{none} of these environment variables are+defined when running under windows. Note also that we refuse to pass+environment variables greater in size than 10k, in order to avoid triggering+\verb!E2BIG! errors.++\begin{code}+definePatches :: RepoPatch p => FL (PatchInfoAnd p) C(x y) -> IO ()+#ifndef WIN32+definePatches ps = do let k = "Defining environment variables"+ beginTedious k+ tediousSize k 3+ finishedOneIO k "DARCS_PATCHES"+ setEnvCautiously "DARCS_PATCHES" (renderString $ Darcs.Patch.summary ps)+ finishedOneIO k "DARCS_PATCHES_XML"+ setEnvCautiously "DARCS_PATCHES_XML"+ (renderString $ text "<patches>" $$+ vcat (mapFL (to_xml . info) ps) $$+ text "</patches>")+ finishedOneIO k "DARCS_FILES"+ setEnvCautiously "DARCS_FILES" (unlines$ list_touched_files ps)+ endTedious k++setEnvCautiously :: String -> String -> IO ()+setEnvCautiously e v | toobig (10*1024) v = return ()+ | otherwise = setEnv e v True+ where toobig :: Int -> [a] -> Bool+ toobig 0 _ = True+ toobig _ [] = False+ toobig n (_:xs) = toobig (n-1) xs+#else+definePatches _ = return ()+#endif++defineChanges :: Patchy p => p C(x y) -> IO ()+#ifndef WIN32+defineChanges ps = setEnvCautiously "DARCS_FILES" (unlines $ list_touched_files ps)+#else+defineChanges _ = return ()+#endif++posthook_cmd :: DarcsOption+posthook_cmd = DarcsMultipleChoiceOption+ [DarcsArgOption [] ["posthook"] PosthookCmd+ "COMMAND" "specify command to run after this darcs command",+ DarcsNoArgOption [] ["no-posthook"] NoPosthook+ "don't run posthook command"]++posthook_prompt :: DarcsOption+posthook_prompt = DarcsMultipleChoiceOption+ [DarcsNoArgOption [] ["prompt-posthook"] AskPosthook+ "prompt before running posthook [DEFAULT]",+ DarcsNoArgOption [] ["run-posthook"] RunPosthook+ "run posthook command without prompting"]++-- | 'get_posthook_cmd' takes a list of flags and returns the posthook command+-- specified by @PosthookCmd a@ in that list of flags, if any.+get_posthook_cmd :: [DarcsFlag] -> Maybe String+get_posthook_cmd (PosthookCmd a:_) = Just a+get_posthook_cmd (_:flags) = get_posthook_cmd flags+get_posthook_cmd [] = Nothing+\end{code}+\begin{options}+--prehook=COMMAND, --no-prehook+\end{options}+To provide a command that should be run before a darcs command is executed,+ use \verb!--prehook! to specify the command. An example use is+for people who want to have a command run whenever a patch is to be recorded, such as+translating line endings before recording patches. Using+\verb!--no-prehook! will disable running the command.+\begin{options}+--run-prehook, --prompt-prehook+\end{options}+These options control prompting before running the prehook. See the+posthook documentation above for details.+\begin{code}+prehook_cmd :: DarcsOption+prehook_cmd = DarcsMultipleChoiceOption+ [DarcsArgOption [] ["prehook"] PrehookCmd+ "COMMAND" "specify command to run before this darcs command",+ DarcsNoArgOption [] ["no-prehook"] NoPrehook+ "don't run prehook command"]++prehook_prompt :: DarcsOption+prehook_prompt = DarcsMultipleChoiceOption+ [DarcsNoArgOption [] ["prompt-prehook"] AskPrehook+ "prompt before running prehook [DEFAULT]",+ DarcsNoArgOption [] ["run-prehook"] RunPrehook+ "run prehook command without prompting"]++-- | 'get_prehook_cmd' takes a list of flags and returns the prehook command+-- specified by @PrehookCmd a@ in that list of flags, if any.+get_prehook_cmd :: [DarcsFlag] -> Maybe String+get_prehook_cmd (PrehookCmd a:_) = Just a+get_prehook_cmd (_:flags) = get_prehook_cmd flags+get_prehook_cmd [] = Nothing+\end{code}++\begin{options}+--ssh-cm, --no-ssh-cm+\end{options}++For commands which invoke ssh, darcs will normally multiplex ssh+sessions over a single connection as long as your version of ssh has+the ControlMaster feature from OpenSSH versions 3.9 and above. This+option will avoid darcs trying to use this feature even if your ssh+supports it.++\begin{options}+--http-pipelining, --no-http-pipelining+\end{options}++When compiled with libcurl (version 7.18.0 and above), darcs can+use HTTP pipelining. It is enabled by default for libcurl+(version 7.19.1 and above). This option will make darcs enable or+disable HTTP pipelining, overwriting default. Note that if HTTP+pipelining is really used depends on the server.++\begin{options}+--no-cache+\end{options}++Do not use patch caches.+\begin{code}+network_options :: [DarcsOption]+network_options =+ [DarcsMultipleChoiceOption+ [DarcsNoArgOption [] ["ssh-cm"] SSHControlMaster+ "use SSH ControlMaster feature",+ DarcsNoArgOption [] ["no-ssh-cm"] NoSSHControlMaster+ "don't use SSH ControlMaster feature [DEFAULT]"],+ DarcsMultipleChoiceOption+ [DarcsNoArgOption [] ["http-pipelining"] HTTPPipelining+ pipelining_description,+ DarcsNoArgOption [] ["no-http-pipelining"] NoHTTPPipelining+ no_pipelining_description],+ no_cache+ ]+ where pipelining_description =+ "enable HTTP pipelining"+++ (if pipeliningEnabledByDefault then " [DEFAULT]" else "")+ no_pipelining_description =+ "disable HTTP pipelining"+++ (if pipeliningEnabledByDefault then "" else " [DEFAULT]")++no_cache :: DarcsOption+no_cache = DarcsNoArgOption [] ["no-cache"] NoCache+ "don't use patch caches"+\end{code}+\begin{options}+--umask+\end{options}+By default, Darcs will use your current umask. The option+\verb|--umask| will cause Darcs to switch to a different umask before+writing to the repository.++\begin{code}+umask_option :: DarcsOption+umask_option =+ DarcsArgOption [] ["umask"] UMask "UMASK"+ "specify umask to use when writing"+\end{code}++\begin{options}+--dont-restrict-paths, --restrict-paths+\end{options}+By default darcs is only allowed to manage and modify files and directories+contained inside the current repository and not being part of any darcs+repository's meta data (including the current one). This is mainly for+security, to protect you from spoofed patches modifying arbitrary files+with sensitive data---say, in your home directory---or tampering with any+repository's meta data to switch off this safety guard.++But sometimes you may want to manage a group of ``sub'' repositories'+preference files with a global repository, or use darcs in some other+advanced way. The best way is probably to put+\verb!ALL dont-restrict-paths! in \verb!_darcs/prefs/defaults!. This turns+off all sanity checking for file paths in patches.++Path checking can be temporarily turned on with \verb!--restrict-paths! on+the command line, when pulling or applying unknown patches.++\begin{code}+restrict_paths =+ DarcsMultipleChoiceOption+ [DarcsNoArgOption [] ["restrict-paths"] RestrictPaths+ "don't allow darcs to touch external files or repo metadata",+ DarcsNoArgOption [] ["dont-restrict-paths"] DontRestrictPaths+ "allow darcs to modify any file or directory (unsafe)"]+\end{code}++\begin{options}+--allow-unrelated-repos+\end{options}+By default darcs checks and warns user if repositories are unrelated when+doing pull, push and send. This option makes darcs skip this check.++\begin{code}+allow_unrelated_repos =+ DarcsNoArgOption [] ["ignore-unrelated-repos"] AllowUnrelatedRepos+ "do not check if repositories are unrelated"+\end{code}++\begin{code}+check, repair, check_or_repair :: DarcsOption+\end{code}++\begin{options}+--check+\end{options}+This option specifies checking mode.++\begin{code}+check =+ DarcsNoArgOption [] ["check"] Check+ "Specify checking mode"+\end{code}++\begin{options}+--repair+\end{options}+This option specifies repair mode.++\begin{code}+repair =+ DarcsNoArgOption [] ["repair"] Repair+ "Specify repair mode"++check_or_repair = concat_options [check, repair]++-- | @'patch_select_flag' f@ holds whenever @f@ is a way of selecting+-- patches such as @PatchName n@.+patch_select_flag :: DarcsFlag -> Bool+patch_select_flag All = True+patch_select_flag (PatchName _) = True+patch_select_flag (OnePatch _) = True+patch_select_flag (SeveralPatch _) = True+patch_select_flag (AfterPatch _) = True+patch_select_flag (UpToPatch _) = True+patch_select_flag (TagName _) = True+patch_select_flag (LastN _) = True+patch_select_flag (OneTag _) = True+patch_select_flag (AfterTag _) = True+patch_select_flag (UpToTag _) = True+patch_select_flag (OnePattern _) = True+patch_select_flag (SeveralPattern _) = True+patch_select_flag (AfterPattern _) = True+patch_select_flag (UpToPattern _) = True+patch_select_flag _ = False++-- | The integer corresponding to a string, if it's only composed of digits.+-- Otherwise, -1.+number_string :: String -> Int+number_string s = if and (map isDigit s) then read s else (-1)+\end{code}
@@ -0,0 +1,27 @@+-- Reporting bugs in darcs. See also impossible.h.+module Darcs.Bug ( _bug, _bugDoc, _impossible, _fromJust+ ) where++import Printer ( Doc, errorDoc, text, ($$) )++type BugStuff = (String, Int, String, String)++_bug :: BugStuff -> String -> a+_bug bs s = _bugDoc bs (text s)++_bugDoc :: BugStuff -> Doc -> a+_bugDoc bs s = errorDoc $+ text ("bug at " ++ _bugLoc bs) $$ s $$+ text ("See http://wiki.darcs.net/index.html/BugTrackerHowto " +++ "for help on bug reporting.")++_bugLoc :: BugStuff -> String+_bugLoc (file, line, date, time) = file++":"++show line++" compiled "++time++" "++date++_impossible :: BugStuff -> a+_impossible bs = _bug bs $ "Impossible case at "++_bugLoc bs++_fromJust :: BugStuff -> Maybe a -> a+_fromJust bs mx =+ case mx of Nothing -> _bug bs $ "fromJust error at "++_bugLoc bs+ Just x -> x
@@ -0,0 +1,59 @@+-- Copyright (C) 2004 David Roundy+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.++module Darcs.CheckFileSystem ( can_I_use_mmap ) where++import System.Directory ( removeFile, removeDirectory, setCurrentDirectory,+ createDirectory,+ )+import Control.Exception ( block )++import Darcs.Utils ( withCurrentDirectory )+import Darcs.Lock ( withOpenTemp )++-- Beware that the below test will return true in any directory where we+-- don't have write permission. This is risky, but means we'll do the+-- right thing in the common case where we're dealing with posix+-- filesystems and directories in which we don't have permission to write.++can_I_remove_open_files :: IO Bool+can_I_remove_open_files = block $ + (withOpenTemp $ \ (_,f) ->+ (do { removeFile f; return True}) `catch` \_ -> return False)+ `catch` \_ -> return True++can_I_remove_directories_holding_open_files :: IO Bool+can_I_remove_directories_holding_open_files = block $+ (do createDirectory "darcs_testing_for_nfs"+ okay <- (withCurrentDirectory "darcs_testing_for_nfs" $+ do withOpenTemp $ \ (_,f) -> + (do removeFile f+ setCurrentDirectory ".."+ removeDirectory "darcs_testing_for_nfs"+ return True+ ) `catch` \_ -> return False+ ) `catch` \_ -> return True+ removeDirectory "darcs_testing_for_nfs" `catch` \_ -> return ()+ return okay+ ) `catch` \_ -> return True++can_I_use_mmap :: IO Bool+can_I_use_mmap = do a <- can_I_remove_open_files+ if a then can_I_remove_directories_holding_open_files+ else return False++
@@ -0,0 +1,281 @@+{-# OPTIONS -fno-warn-orphans #-}+module Darcs.ColorPrinter ( errorDoc, traceDoc, assertDoc, fancyPrinters ) where++import Debug.Trace ( trace )+import System.IO ( stderr )+import Darcs.External (getTermNColors)+import Printer (Printer, Printers, Printers'(..), Printable(..), Color(..),+ invisiblePrinter, (<>), (<?>), Doc(Doc,unDoc), unsafeBothText, simplePrinter, hcat,+ unsafeText, unsafeChar, space, unsafePackedString,+ renderStringWith, prefix )+import Data.Char ( isAscii, isPrint, isSpace, isControl, ord, chr, intToDigit )+import Data.Bits ( bit, xor )+import System.Environment ( getEnv )+import qualified Data.ByteString.Char8 as BC (unpack, any, last, spanEnd)+import qualified Data.ByteString as B (null, init)+import System.IO.Unsafe ( unsafePerformIO )+import System.IO ( hIsTerminalDevice, Handle )++dollar, cr :: Doc+dollar = unsafeBothText "$"+cr = unsafeBothText "\r"++errorDoc :: Doc -> a+errorDoc = error . show++traceDoc :: Doc -> a -> a+traceDoc d = trace (show d)++assertDoc :: Maybe Doc -> a -> a+assertDoc Nothing x = x+assertDoc (Just e) _ = errorDoc e++instance Show Doc where+ show = renderStringWith (fancyPrinters stderr)++-- policy+-- | the 'Policy' type is a record containing the variables which control+-- how 'Doc's will be rendered on some output.+data Policy = Policy { poColor :: Bool -- ^ overall use of color+ , poEscape :: Bool -- ^ overall use of escaping+ , poLineColor :: Bool -- ^ overall use of colored lines (only hunks for now)+ , poAltColor :: Bool -- ^ alternative to color (bold, inverse)+ , poIsprint :: Bool -- ^ don't escape isprints+ , po8bit :: Bool -- ^ don't escape 8-bit chars+ , poNoEscX :: String -- ^ extra chars to never escape+ , poEscX :: String -- ^ extra chars to always escape+ , poTrailing :: Bool -- ^ escape trailing spaces+ , poCR :: Bool -- ^ ignore \r at end of lines+ , poSpace :: Bool -- ^ escape spaces (used with poTrailing)+ }++{-# NOINLINE getPolicy #-}+-- | 'getPolicy' returns a suitable policy for a given handle.+-- The policy is chosen according to environment variables, and to the+-- type of terminal which the handle represents+getPolicy :: Handle -> Policy+getPolicy handle = unsafePerformIO $+ do isTerminal <- hIsTerminalDevice handle+ nColors <- if isTerminal then getTermNColors else return 0++ envDontEscapeAnything <- getEnvBool "DARCS_DONT_ESCAPE_ANYTHING"+ envDontEscapeIsprint <- getEnvBool "DARCS_DONT_ESCAPE_ISPRINT"+ envUseIsprint <- getEnvBool "DARCS_USE_ISPRINT" -- for backwards-compatibility+ envDontEscape8bit <- getEnvBool "DARCS_DONT_ESCAPE_8BIT"++ envDontEscapeExtra <- getEnvString "DARCS_DONT_ESCAPE_EXTRA"+ envEscapeExtra <- getEnvString "DARCS_ESCAPE_EXTRA"++ envDontEscapeTrailingSpace <- getEnvBool "DARCS_DONT_ESCAPE_TRAILING_SPACES"+ envDontEscapeTrailingCR <- getEnvBool "DARCS_DONT_ESCAPE_TRAILING_CR"++ envDontColor <- getEnvBool "DARCS_DONT_COLOR"+ envAlwaysColor <- getEnvBool "DARCS_ALWAYS_COLOR"+ envAlternativeColor <- getEnvBool "DARCS_ALTERNATIVE_COLOR"+ envDoColorLines <- getEnvBool "DARCS_DO_COLOR_LINES"++ let haveColor = envAlwaysColor || (isTerminal && (nColors > 4))+ doColor = not envDontColor && haveColor++ return Policy { poColor = doColor,+ poEscape = not envDontEscapeAnything,+ poLineColor= doColor && envDoColorLines,+ poIsprint = envDontEscapeIsprint || envUseIsprint,+ po8bit = envDontEscape8bit,+ poNoEscX = envDontEscapeExtra,+ poEscX = envEscapeExtra,+ poTrailing = not envDontEscapeTrailingSpace,+ poCR = envDontEscapeTrailingCR,+ poAltColor = haveColor && envAlternativeColor,++ poSpace = False+ }+ where+ getEnvBool s = safeGetEnv s >>= return.(/= "0")+ safeGetEnv s = getEnv s `catch` \_ -> return "0"+ getEnvString s = getEnv s `catch` \_ -> return ""+++-- printers++-- | @'fancyPrinters' h@ returns a set of printers suitable for outputting+-- to @h@+fancyPrinters :: Printers+fancyPrinters h = let policy = getPolicy h in + Printers { colorP = colorPrinter policy,+ invisibleP = invisiblePrinter,+ hiddenP = colorPrinter policy Green,+ userchunkP = userchunkPrinter policy,+ defP = escapePrinter policy,+ lineColorT = lineColorTrans policy,+ lineColorS = lineColorSuffix policy+ }++-- | @'lineColorTrans' policy@ tries to color a Doc, according to policy po.+-- That is, if @policy@ has @poLineColor@ set, then colors the line, otherwise+-- does nothing.+lineColorTrans :: Policy -> Color -> Doc -> Doc+lineColorTrans po | poLineColor po = \c d -> prefix (set_color c) d <?> unsafeBothText reset_color+ | otherwise = const id++lineColorSuffix :: Policy -> [Printable] -> [Printable]+lineColorSuffix po | poLineColor po = \d -> S reset_color : d+ | otherwise = id++colorPrinter :: Policy -> Color -> Printer+colorPrinter po | poColor po = \c -> unDoc . color po c . Doc . escapePrinter po{poColor=False}+ | otherwise = const $ escapePrinter po++userchunkPrinter :: Policy -> Printer+userchunkPrinter po p+ | not (poEscape po) = simplePrinter p+ | not (poTrailing po) = escapePrinter po p+ | otherwise = unDoc $ pr p+ where+ pr (S s) = prString s+ pr (Both _ ps) = prPS ps+ pr (PS ps) = prPS ps++ prPS ps = let (leadPS, trailPS) = BC.spanEnd isSpace ps+ in if B.null trailPS+ then Doc $ escapePrinter po p+ else Doc (escapePrinter po (PS leadPS))+ <> Doc (escapePrinter po{poSpace=True} (PS trailPS))+ <> mark_escape po dollar++ prString s = let (trail',lead') = span isSpace (reverse s)+ lead = reverse lead'+ trail = reverse trail'+ in if (not.null) trail+ then Doc (escapePrinter po (S lead))+ <> Doc (escapePrinter po{poSpace=True} (S trail))+ <> mark_escape po dollar+ else Doc (escapePrinter po p)++escapePrinter :: Policy -> Printer+escapePrinter po+ | (not.poEscape) po = simplePrinter+ | otherwise = unDoc . crepr+ where+ crepr p | poCR po && isEndCR p = epr (initPR p) <> cr+ | otherwise = epr p++ epr (S s) = escape po s+ epr (PS ps) = if BC.any (not.no_escape po) ps+ then escape po (BC.unpack ps)+ else unsafePackedString ps+ epr (Both s _) = escape po s++ isEndCR (S s) = not (null s) && last s == '\r'+ isEndCR (PS ps) = not (B.null ps) && BC.last ps == '\r'+ isEndCR (Both _ ps) = not (B.null ps) && BC.last ps == '\r'++ initPR (S s) = S $ init s+ initPR (PS ps) = PS $ B.init ps+ initPR (Both s ps) = Both (init s) (B.init ps)+++-- escape assumes the input is in ['\0'..'\255']++-- | @'escape' policy string@ escapes @string@ according to the rules+-- defined in 'policy', turning it into a 'Doc'.+escape :: Policy -> String -> Doc+escape _ "" = unsafeText ""+escape po s = hcat (map escapeChar s)+ where+ escapeChar c | no_escape po c = unsafeChar c+ escapeChar ' ' = space+ escapeChar c = (emph.unsafeText.quoteChar) c+ emph = mark_escape po++-- | @'no_escape' policy c@ tells wether @c@ will be left as-is+-- when escaping according to @policy@+no_escape :: Policy -> Char -> Bool+no_escape po c | poSpace po && isSpace c = False+no_escape po c | c `elem` poEscX po = False+no_escape po c | c `elem` poNoEscX po = True+no_escape _ '\t' = True -- tabs will likely be converted to spaces+no_escape _ '\n' = True+no_escape po c = if (poIsprint po) then isPrint c+ else isPrintableAscii c+ || c >= '\x80' && po8bit po++-- | 'isPrintableAscii' tells wether a character is a printable character+-- of the ascii range.+isPrintableAscii :: Char -> Bool+isPrintableAscii c = isAscii c && isPrint c+++-- | 'quoteChar' represents a special character as a string.+-- * @quoteChar '^c'@ (where @^c@ is a control character) is @"^c"@+-- * Otherwise, @quoteChar@ returns "\hex", where 'hex' is the+-- hexadecimal number of the character.+quoteChar :: Char -> String+quoteChar c+ | isControl c && isPrintableAscii cHat = ['^', cHat]+ | otherwise = sHex+ where+ cHat = chr $ (bit 6 `xor`) $ ord c+ sHex = let (q, r) = quotRem (ord c) 16+ in ['\\', intToDigit q, intToDigit r]+++-- make colors and highlightings++-- | @'mark_escape' policy doc@ marks @doc@ with the appropriate+-- marking for escaped characters according to @policy@+mark_escape :: Policy -> Doc -> Doc+mark_escape po | poAltColor po = make_invert+ | poColor po = make_color Red+ | otherwise = make_asciiart++-- | @'color' policy color doc@ colors @doc@ with color @color@ if+-- @policy@ is not set to use an alternative to color. In that case,+-- it makes the text bold instead.+color :: Policy -> Color -> Doc -> Doc+color po | poAltColor po = \_ -> make_bold+ | otherwise = make_color++make_color, make_color' :: Color -> Doc -> Doc++make_color' = with_color . set_color++-- memoized version of make_color'+make_color Blue = make_color' Blue+make_color Red = make_color' Red+make_color Green = make_color' Green+make_color Cyan = make_color' Cyan+make_color Magenta = make_color' Magenta++set_color :: Color -> String+set_color Blue = "\x1B[01;34m" -- bold blue+set_color Red = "\x1B[01;31m" -- bold red+set_color Green = "\x1B[01;32m" -- bold green+set_color Cyan = "\x1B[36m" -- light cyan+set_color Magenta = "\x1B[35m" -- light magenta++-- | @'make_asciiart' doc@ tries to make @doc@ (usually a+-- single escaped char) stand out with the help of only plain+-- ascii, i.e., no color or font style.+make_asciiart :: Doc -> Doc+make_asciiart x = unsafeBothText "[_" <> x <> unsafeBothText "_]"++-- | the string to reset the terminal's color.+reset_color :: String+reset_color = "\x1B[00m"++-- | @'with_color' color doc@ returns a colorized version of @doc@.+-- @color@ is a string that represents a color, given by 'set_color'+with_color :: String -> Doc -> Doc+with_color c =+ let c' = unsafeBothText c+ r' = unsafeBothText reset_color+ in \x -> c' <> x <> r'+++-- | 'make_bold' boldens a doc.+make_bold :: Doc -> Doc+-- | 'make_invert' returns an invert video version of a doc.+make_invert :: Doc -> Doc+make_bold = with_color "\x1B[01m"+make_invert = with_color "\x1B[07m"
@@ -0,0 +1,294 @@+% Copyright (C) 2002,2003,2005 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\begin{code}+module Darcs.Commands ( CommandControl( Command_data, Hidden_command, Group_name ),+ DarcsCommand( DarcsCommand, command_name,+ command_help, command_description,+ command_basic_options, command_advanced_options,+ command_command,+ command_prereq,+ command_extra_arg_help,+ command_extra_args,+ command_argdefaults,+ command_get_arg_possibilities,+ SuperCommand,+ command_sub_commands ),+ command_alias, command_stub,+ command_options, command_alloptions,+ disambiguate_commands, CommandArgs(..),+ get_command_help, get_command_mini_help,+ get_subcommands,+ usage, subusage, chomp_newline,+ extract_commands,+ super_name,+ nodefaults,+ loggers,+ ) where++import System.Console.GetOpt( OptDescr, usageInfo )++import Data.List ( sort, isPrefixOf )+import Darcs.Arguments ( DarcsFlag, DarcsOption, disable, help,+ any_verbosity, posthook_cmd, posthook_prompt,+ prehook_cmd, prehook_prompt, option_from_darcsoption )+import Darcs.RepoPath ( AbsolutePath, rootDirectory )+import Darcs.Utils ( putStrLnError )+import Printer ( Doc, putDocLn )+\end{code}++The general format of a darcs command is+\begin{verbatim}+% darcs COMMAND OPTIONS ARGUMENTS ...+\end{verbatim}+Here \verb|COMMAND| is a command such as \verb|add| or \verb|record|, which of+course may have one or more arguments. Options have the form+\verb!--option! or \verb!-o!, while arguments vary from command to+command. There are many options which are common to a number of different+commands, which will be summarized here.++If you wish, you may use any unambiguous beginning of a command name as a+shortcut: for \verb!darcs record!, you could type \verb!darcs recor! or+\verb!darcs rec!, but not \verb!darcs re! since that could be confused with+\verb!darcs replace!, \verb!darcs revert! and \verb!darcs remove!.++In some cases, \verb|COMMAND| actually consists of two words, a+super-command and a subcommand. For example, the ``display the+manifest'' command has the form \verb|darcs query manifest|.++\paragraph{Command overview}++Not all commands modify the ``patches'' of your repository (that+is, the named patches which other users can pull); some commands only+affect the copy of the source tree you're working on (your ``working+directory''), and some affect both. This table summarizes what you should+expect from each one and will hopefully serve as guide when you're having+doubts about which command to use.++\begin{center}+\footnotetext[1]{But it affects the repository and working directory targeted+ by the push}+\footnotetext[2]{As for the other end, see apply}+\begin{tabular}{|c|c|c|}+\hline+affects & patches & working directory\\+\hline+record & yes & no\\+\hline+unrecord & yes & no\\+\hline+rollback & yes & yes\\+\hline+revert & no & yes\\+\hline+unrevert & no & yes\\+\hline+pull & yes & yes\\+\hline+obliterate & yes & yes\\+\hline+apply & yes & yes\\+\hline+push\footnote{But it affects the repository and working directory targeted by+the push} & no & no\\+\hline+send\footnote{As for the other end, see apply} & no & no\\+\hline+put\footnote{Creates a new repository} & no & no\\+\hline+\end{tabular}+\end{center}++\begin{code}+extract_commands, extract_hidden_commands :: [CommandControl] -> [DarcsCommand]+extract_commands cs = concatMap (\x -> case x of { Command_data cmd_d -> [cmd_d]; _ -> []}) cs+extract_hidden_commands cs = concatMap (\x -> case x of { Hidden_command cmd_d -> [cmd_d]; _ -> []}) cs+\end{code}++\input{Darcs/Arguments.lhs}++\begin{code}+data CommandControl = Command_data DarcsCommand+ | Hidden_command DarcsCommand+ | Group_name String++data DarcsCommand =+ DarcsCommand {command_name, command_help, command_description :: String,+ command_extra_args :: Int,+ command_extra_arg_help :: [String],+ command_command :: [DarcsFlag] -> [String] -> IO (),+ command_prereq :: [DarcsFlag] -> IO (Either String ()),+ command_get_arg_possibilities :: IO [String],+ command_argdefaults :: [DarcsFlag] -> AbsolutePath -> [String] -> IO [String],+ command_basic_options :: [DarcsOption],+ command_advanced_options :: [DarcsOption]}+ | SuperCommand {command_name, command_help, command_description :: String,+ command_prereq :: [DarcsFlag] -> IO (Either String ()),+ command_sub_commands :: [CommandControl]}++command_alloptions :: DarcsCommand -> ([DarcsOption], [DarcsOption])+command_alloptions DarcsCommand { command_basic_options = opts1+ , command_advanced_options = opts2 }+ = (opts1 ++ [disable, help],+ any_verbosity ++ opts2 +++ [posthook_cmd, posthook_prompt+ ,prehook_cmd, prehook_prompt])++-- Supercommands cannot be disabled.+command_alloptions SuperCommand { } = ([help],[])++-- Obtain options suitable as input to+-- System.Console.Getopt, including the --disable option (which is+-- not listed explicitly in the DarcsCommand definitions).+command_options :: AbsolutePath -> DarcsCommand -> ([OptDescr DarcsFlag], [OptDescr DarcsFlag])+command_options cwd c = (convert basic, convert advanced)+ where (basic, advanced) = command_alloptions c+ convert = concatMap (option_from_darcsoption cwd)++nodefaults :: [DarcsFlag] -> AbsolutePath -> [String] -> IO [String]+nodefaults _ _ xs = return xs++get_subcommands :: DarcsCommand -> [CommandControl]+get_subcommands c@(SuperCommand {}) = command_sub_commands c+get_subcommands _ = []++command_alias :: String -> DarcsCommand -> DarcsCommand+command_alias n c =+ c { command_name = n+ , command_description = "Alias for `darcs " ++ command_name c ++ "'."+ , command_help = "The `darcs " ++ n ++ "' command is an alias for " +++ "`darcs " ++ command_name c ++ "'.\n" +++ command_help c+ }++command_stub :: String -> String -> String -> DarcsCommand -> DarcsCommand+command_stub n h d c =+ c { command_name = n+ , command_help = h+ , command_description = d+ , command_command = \_ _ -> putStr h+ }++usage :: [CommandControl] -> String+usage cs = "Usage: darcs COMMAND ...\n\nCommands:\n" +++ usage_helper cs ++ "\n" +++ "Use 'darcs COMMAND --help' for help on a single command.\n" +++ "Use 'darcs --version' to see the darcs version number.\n" +++ "Use 'darcs --exact-version' to get the exact version of this darcs instance.\n" +++ "Use 'darcs help patterns' for help on patch matching.\n" +++ "Use 'darcs help environment' for help on environment variables.\n" +++ "\n" +++ "Check bug reports at http://bugs.darcs.net/\n"++subusage :: DarcsCommand -> String+subusage super =+ (usageInfo+ ("Usage: darcs "++command_name super++" SUBCOMMAND ... " +++ "\n\n"++ command_description super+++ "\n\nSubcommands:\n" ++ usage_helper (get_subcommands super) ++ "\nOptions:")+ (option_from_darcsoption rootDirectory help))+ ++ "\n" ++ command_help super++usage_helper :: [CommandControl] -> String+usage_helper [] = ""+usage_helper (Hidden_command _:cs) = usage_helper cs+usage_helper ((Command_data c):cs) = " "++pad_spaces (command_name c) 15 +++ chomp_newline (command_description c)++"\n"++usage_helper cs+usage_helper ((Group_name n):cs) = "\n" ++ n ++ "\n" ++ usage_helper cs++chomp_newline :: String -> String+chomp_newline "" = ""+chomp_newline s = if last s == '\n' then init s else s++pad_spaces :: String -> Int -> String+pad_spaces s n = s ++ replicate (n - length s) ' '++super_name :: Maybe DarcsCommand -> String+super_name Nothing = ""+super_name (Just x) = command_name x ++ " "++get_command_mini_help :: Maybe DarcsCommand -> DarcsCommand -> String+get_command_mini_help msuper cmd =+ get_command_help_core msuper cmd +++ "\n\nSee darcs help "+ ++ (maybe "" (\c -> command_name c ++ " ") msuper)+ ++ command_name cmd ++ " for details."++get_command_help :: Maybe DarcsCommand -> DarcsCommand -> String+get_command_help msuper cmd =+ unlines (reverse basicR)+ ++ (if null advanced then ""+ else "\nAdvanced options:\n" ++ unlines (reverse advancedR))+ ++ "\n" ++ command_help cmd+ where -- we could just call usageInfo twice, but then the advanced+ -- options might not line up with the basic ones (no short flags)+ (advancedR, basicR) =+ splitAt (length advanced) $ reverse $ lines combinedUsage+ combinedUsage = usageInfo+ (get_command_help_core msuper cmd ++ subcommands ++ "\n\nOptions:")+ (basic ++ advanced)+ (basic, advanced) = command_options rootDirectory cmd+ subcommands =+ case msuper of+ Nothing -> case get_subcommands cmd of+ [] -> []+ s -> "\n\nSubcommands:\n" ++ (usage_helper s)+ -- we don't want to list subcommands if we're already specifying them+ Just _ -> ""++get_command_help_core :: Maybe DarcsCommand -> DarcsCommand -> String+get_command_help_core msuper cmd =+ "Usage: darcs "++super_name msuper++command_name cmd+++ " [OPTION]... " ++ unwords args_help +++ "\n"++ command_description cmd+ where args_help = case cmd of+ (DarcsCommand _ _ _ _ _ _ _ _ _ _ _) ->+ command_extra_arg_help cmd+ _ -> []++data CommandArgs = CommandOnly DarcsCommand+ | SuperCommandOnly DarcsCommand+ | SuperCommandSub DarcsCommand DarcsCommand++-- Parses a darcs command line with potentially abbreviated commands+disambiguate_commands :: [CommandControl] -> String -> [String]+ -> Either String (CommandArgs, [String])+disambiguate_commands allcs cmd args =+ do c <- extract cmd allcs+ case (get_subcommands c, args) of+ ([], _) -> return (CommandOnly c, args)+ (_ ,[]) -> return (SuperCommandOnly c, args)+ (subcs, (a:as)) -> case extract a subcs of+ Left _ -> return (SuperCommandOnly c, args)+ Right sc -> return (SuperCommandSub c sc, as)++extract :: String -> [CommandControl] -> Either String DarcsCommand+extract cmd cs =+ case [ c | c <- extract_commands cs, cmd `isPrefixOf` command_name c ] +++ [ h | h <- extract_hidden_commands cs, cmd == command_name h ] of+ [] -> Left $ "No such command '" ++ cmd ++ "'\n"+ [c] -> Right c+ cs' -> Left $ "Ambiguous command...\n\n" +++ "The command '"++cmd++"' could mean one of:\n" +++ unwords (sort $ map command_name cs')++-- | Output functions equivalent to (putStrLn, hPutStrLn stderr, putDocLn)+loggers :: [DarcsFlag] -> ( String -> IO ()+ , String -> IO ()+ , Doc -> IO ())+loggers _ = (putStrLn, putStrLnError, putDocLn)+\end{code}
@@ -0,0 +1,278 @@+% Copyright (C) 2002-2004 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\darcsCommand{add}+\begin{code}+module Darcs.Commands.Add ( add ) where++import Data.List ( (\\), nub)++import Darcs.Commands+import Darcs.Arguments (noskip_boring, allow_problematic_filenames,+ fancy_move_add,+ recursive, working_repo_dir, dry_run_noxml, umask_option,+ list_files, list_unregistered_files,+ DarcsFlag (AllowCaseOnly, AllowWindowsReserved, Boring, Recursive,+ Verbose, Quiet, FancyMoveAdd, DryRun),+ fixSubPaths,+ )+import Darcs.Utils ( withCurrentDirectory, nubsort )+import IsoDate ( getIsoDateTime )+import Darcs.Repository ( amInRepository, withRepoLock, ($-),+ slurp_pending, add_to_pending )+import Darcs.Patch ( Prim, apply_to_slurpy, addfile, adddir, move )+import Darcs.Ordered ( FL(..), unsafeFL, concatFL, nullFL )+import Darcs.SlurpDirectory ( Slurpy, slurp_has_anycase, slurp_has,+ isFileReallySymlink, doesDirectoryReallyExist, + doesFileReallyExist, slurp_hasdir,+ )+import Darcs.Patch.FileName ( fp2fn )+import Darcs.RepoPath ( toFilePath )+import Control.Monad ( when, unless )+import Darcs.Repository.Prefs ( darcsdir_filter, boring_file_filter )+import Data.Maybe ( maybeToList )+import System.FilePath.Posix ( takeDirectory, (</>) )+import System.IO ( hPutStrLn, stderr )+import qualified System.FilePath.Windows as WindowsFilePath+import Darcs.Gorsvet( invalidateIndex )++add_description :: String+add_description = "Add one or more new files or directories."++add_help :: String+add_help =+ "Generally a repository contains both files that should be version\n" +++ "controlled (such as source code) and files that Darcs should ignore\n" +++ "(such as executables compiled from the source code). The `darcs add'\n" +++ "command is used to tell Darcs which files to version control.\n" +++ "\n" +++ "When an existing project is first imported into a Darcs repository, it\n" +++ "is common to run `darcs add -r *' or `darcs record -l' to add all\n" +++ "initial source files into darcs.\n"+++ "\n" +++ "Adding symbolic links (symlinks) is not supported.\n\n"++add :: DarcsCommand+add = DarcsCommand {command_name = "add",+ command_help = add_help ++ add_help' ++ add_help'',+ command_description = add_description,+ command_extra_args = -1,+ command_extra_arg_help = ["<FILE or DIRECTORY> ..."],+ command_command = add_cmd,+ command_prereq = amInRepository,+ command_get_arg_possibilities = list_unregistered_files,+ command_argdefaults = nodefaults,+ command_advanced_options = [umask_option],+ command_basic_options =+ [noskip_boring, allow_problematic_filenames,+ recursive "add contents of subdirectories",+ fancy_move_add,+ working_repo_dir, dry_run_noxml]}++add_help' :: String+add_help' =+ "Darcs will ignore all files and folders that look `boring'. The\n" +++ "--boring option overrides this behaviour.\n" +++ "\n" +++ "Darcs will not add file if another file in the same folder has the\n" +++ "same name, except for case. The --case-ok option overrides this\n" +++ "behaviour. Windows and OS X usually use filesystems that do not allow\n" +++ "files a folder to have the same name except for case (for example,\n" +++ "`ReadMe' and `README'). If --case-ok is used, the repository might be\n" +++ "unusable on those systems!\n\n"++add_cmd :: [DarcsFlag] -> [String] -> IO ()+add_cmd opts args = withRepoLock opts $- \repository ->+ do cur <- slurp_pending repository+ origfiles <- map toFilePath `fmap` fixSubPaths opts args+ when (null origfiles) $+ putStrLn "Nothing specified, nothing added." >>+ putStrLn "Maybe you wanted to say `darcs add --recursive .'?"+ parlist <- get_parents cur origfiles+ flist' <- if Recursive `elem` opts+ then expand_dirs origfiles+ else return origfiles+ let flist = nubsort (parlist ++ flist')+ -- refuse to add boring files recursively:+ nboring <- if Boring `elem` opts+ then return darcsdir_filter+ else boring_file_filter+ let putInfoLn = if Quiet `elem` opts then \_ -> return () else putStrLn+ mapM_ (putInfoLn . ((msg_skipping msgs ++ " boring file ")++)) $+ flist \\ nboring flist+ date <- getIsoDateTime+ invalidateIndex repository+ ps <- addp msgs opts date cur $ nboring flist+ when (nullFL ps && not (null args)) $+ fail "No files were added"+ unless gotDryRun $ add_to_pending repository ps+ where+ gotDryRun = DryRun `elem` opts+ msgs | gotDryRun = dryRunMessages+ | otherwise = normalMessages++addp :: AddMessages -> [DarcsFlag] -> String -> Slurpy -> [FilePath] -> IO (FL Prim)+addp msgs opts date cur0 files = do+ (ps, dups) <-+ foldr+ (\f rest cur accPS accDups -> do+ (cur', mp, mdup) <- addp' cur f+ rest cur' (maybeToList mp ++ accPS) (maybeToList mdup ++ accDups))+ (\_ ps dups -> return (reverse ps, dups))+ files+ cur0 [] []+ let uniq_dups = nub dups+ caseMsg =+ if gotAllowCaseOnly then ":"+ else ";\nnote that to ensure portability we don't allow\n" +++ "files that differ only in case. Use --case-ok to override this:"+ unless (null dups) $ do+ dupMsg <-+ case uniq_dups of+ [f] ->+ do+ isDir <- doesDirectoryReallyExist f+ if isDir+ then return $+ "The following directory "++msg_is msgs++" already in the repository"+ else return $+ "The following file "++msg_is msgs++" already in the repository"+ fs ->+ do+ areDirs <- mapM doesDirectoryReallyExist fs+ if and areDirs+ then return $+ "The following directories "++msg_are msgs++" already in the repository"+ else+ (if or areDirs+ then return $+ "The following files and directories " +++ msg_are msgs ++ " already in the repository"+ else return $+ "The following files " ++ msg_are msgs ++ " already in the repository")+ putInfo $ dupMsg ++ caseMsg+ mapM_ putInfo uniq_dups+ return $ concatFL $ unsafeFL ps+ where+ addp' :: Slurpy -> FilePath -> IO (Slurpy, Maybe (FL Prim), Maybe FilePath)+ addp' cur f =+ if already_has+ then return (cur, Nothing, Just f)+ else+ if is_badfilename+ then do putInfo $ "The filename " ++ f ++ " is invalid under Windows.\nUse --reserved-ok to allow it."+ return add_failure+ else do+ isdir <- doesDirectoryReallyExist f+ if isdir+ then trypatch $ myadddir f+ else do isfile <- doesFileReallyExist f+ if isfile+ then trypatch $ myaddfile f+ else do islink <- isFileReallySymlink f+ if islink then+ putInfo $ "Sorry, file " ++ f ++ " is a symbolic link, which is unsupported by darcs."+ else putInfo $ "File "++ f ++" does not exist!"+ return add_failure+ where already_has = if gotAllowCaseOnly+ then slurp_has f cur+ else slurp_has_anycase f cur+ is_badfilename = not (gotAllowWindowsReserved || WindowsFilePath.isValid f)+ add_failure = (cur, Nothing, Nothing)+ trypatch p =+ case apply_to_slurpy p cur of+ Nothing -> do putInfo $ msg_skipping msgs ++ " '" ++ f ++ "' ... " ++ parent_error+ return (cur, Nothing, Nothing)+ Just s' -> do putVerbose $ msg_adding msgs++" '"++f++"'"+ return (s', Just p, Nothing)+ parentdir = takeDirectory f+ have_parentdir = slurp_hasdir (fp2fn parentdir) cur+ parent_error = if have_parentdir+ then ""+ else "couldn't add parent directory '"++parentdir+++ "' to repository."+ myadddir d = if gotFancyMoveAdd+ then adddir (d++"-"++date) :>:+ move (d++"-"++date) d :>: NilFL+ else adddir d :>: NilFL+ myaddfile d = if gotFancyMoveAdd+ then addfile (d++"-"++date) :>:+ move (d++"-"++date) d :>: NilFL+ else addfile d :>: NilFL+ putVerbose = if Verbose `elem` opts || DryRun `elem` opts+ then putStrLn+ else \_ -> return ()+ putInfo = if Quiet `elem` opts then \_ -> return () else hPutStrLn stderr+ gotFancyMoveAdd = FancyMoveAdd `elem` opts+ gotAllowCaseOnly = AllowCaseOnly `elem` opts+ gotAllowWindowsReserved = AllowWindowsReserved `elem` opts++data AddMessages =+ AddMessages+ { msg_skipping :: String+ , msg_adding :: String+ , msg_is :: String+ , msg_are :: String+ }++normalMessages, dryRunMessages :: AddMessages+normalMessages =+ AddMessages+ { msg_skipping = "Skipping"+ , msg_adding = "Adding"+ , msg_is = "is"+ , msg_are = "are"+ }+dryRunMessages =+ AddMessages+ { msg_skipping = "Would skip"+ , msg_adding = "Would add"+ , msg_is = "would be"+ , msg_are = "would be"+ }++-- |FIXME: this documentation makes *no* sense to me, and the+-- ramifications of using this option are not clear. --twb, 2008+add_help'' :: String+add_help'' =+ "The --date-trick option allows you to enable an experimental trick to\n" +++ "make add conflicts, in which two users each add a file or directory\n" +++ "with the same name, less problematic. While this trick is completely\n" +++ "safe, it is not clear to what extent it is beneficial.\n"++expand_dirs :: [FilePath] -> IO [FilePath]+expand_dirs fs = concat `fmap` mapM expand_one fs+expand_one :: FilePath -> IO [FilePath]+expand_one "" = list_files+expand_one f = do+ isdir <- doesDirectoryReallyExist f+ if not isdir then return [f]+ else do fs <- withCurrentDirectory f list_files+ return $ f: map (f </>) fs++get_parents :: Slurpy -> [FilePath] -> IO [FilePath]+get_parents cur fs =+ concat `fmap` mapM (get_parent cur) fs+get_parent :: Slurpy -> FilePath -> IO [FilePath]+get_parent cur f =+ if slurp_hasdir (fp2fn parentdir) cur+ then return []+ else do grandparents <- get_parent cur parentdir+ return (grandparents ++ [parentdir])+ where parentdir = takeDirectory f+\end{code}+
@@ -0,0 +1,195 @@+% Copyright (C) 2004,2007 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\darcsCommand{amend-record}+\begin{code}+module Darcs.Commands.AmendRecord ( amendrecord ) where+import Data.List ( sort )+import Data.Maybe ( isJust )+import System.Exit ( ExitCode(..), exitWith )+import Control.Monad ( when )++import Darcs.Flags ( DarcsFlag(Author, LogFile, PatchName,+ EditLongComment, PromptLongComment) )+import Darcs.Lock ( world_readable_temp )+import Darcs.RepoPath ( toFilePath, sp2fn )+import Darcs.Hopefully ( PatchInfoAnd, n2pia, hopefully, info )+import Darcs.Repository ( withRepoLock, ($-), withGutsOf,+ get_unrecorded_in_files, get_unrecorded_in_files_unsorted,+ tentativelyRemovePatches, tentativelyAddPatch, finalizeRepositoryChanges,+ sync_repo, amInRepository,+ )+import Darcs.Patch ( RepoPatch, description, Prim, fromPrims,+ infopatch, getdeps, adddeps, effect,+ sort_coalesceFL,+ canonize )+import Darcs.Patch.Info ( pi_author, pi_name, pi_log,+ PatchInfo, patchinfo, is_inverted, invert_name,+ )+import Darcs.Ordered ( FL(..), (:>)(..), (+>+),+ nullFL, mapFL_FL, concatFL )+import Darcs.SelectChanges ( with_selected_changes_to_files',+ with_selected_patch_from_repo )+import Darcs.Commands ( DarcsCommand(..), nodefaults )+import Darcs.Commands.Record ( get_date, get_log )+import Darcs.Arguments ( DarcsFlag ( All ),+ areFileArgs, fixSubPaths, defineChanges,+ all_interactive, ignoretimes,+ ask_long_comment, author, patchname_option,+ leave_test_dir, nocompress, lookforadds,+ working_repo_dir,+ match_one_nontag, umask_option,+ notest, testByDefault, list_registered_files,+ get_easy_author, set_scripts_executable+ )+import Darcs.Utils ( askUser )+import Printer ( putDocLn )+import Darcs.Gorsvet( invalidateIndex )++amendrecord_description :: String+amendrecord_description =+ "Improve a patch before it leaves your repository."++amendrecord_help :: String+amendrecord_help =+ "Amend-record updates a `draft' patch with additions or improvements,\n" +++ "resulting in a single `finished' patch. This is better than recording\n" +++ "the additions and improvements as separate patches, because then\n" +++ "whenever the `draft' patch is copied between repositories, you would\n" +++ "need to make sure all the extra patches are copied, too.\n" +++ "\n" +++ "Do not copy draft patches between repositories, because a finished\n" +++ "patch cannot be copied into a repository that contains a draft of the\n" +++ "same patch. If this has already happened, `darcs obliterate' can be\n" +++ "used to remove the draft patch.\n" +++ "\n" +++ -- FIXME: is the following still true in Darcs 2.1? --twb+ "Do not run amend-record in repository that other developers can pull\n" +++ "from, because if they pull while an amend-record is in progress, their\n" +++ "repository may be corrupted.\n" +++ "\n" +++ "When recording a draft patch, it is a good idea to start the name with\n" +++ "`DRAFT:' so that other developers know it is not finished. When\n" +++ "finished, remove it with `darcs amend-record --edit-long-comment'.\n" +++ "To change the patch name without starting an editor, use --patch-name.\n" +++ "\n" +++ "Like `darcs record', if you call amend-record with files as arguments,\n" +++ "you will only be asked about changes to those files. So to amend a\n" +++ "patch to foo.c with improvements in bar.c, you would run:\n" +++ "\n" +++ " darcs amend-record --match 'touch foo.c' bar.c\n" +++ "\n" +++ "It is usually a bad idea to amend another developer's patch. To make\n" +++ "amend-record only ask about your own patches by default, you can add\n" +++ "something like `amend-record match David Roundy' to ~/.darcs/defaults, \n" +++ "where `David Roundy' is your name. " ++ + "On Windows use C:/Documents And Settings/user/Application Data/darcs/defaults\n"++amendrecord :: DarcsCommand+amendrecord = DarcsCommand {command_name = "amend-record",+ command_help = amendrecord_help,+ command_description = amendrecord_description,+ command_extra_args = -1,+ command_extra_arg_help = ["[FILE or DIRECTORY]..."],+ command_command = amendrecord_cmd,+ command_prereq = amInRepository,+ command_get_arg_possibilities = list_registered_files,+ command_argdefaults = nodefaults,+ command_advanced_options = [nocompress, ignoretimes, umask_option,+ set_scripts_executable],+ command_basic_options = [match_one_nontag,+ notest,+ leave_test_dir,+ all_interactive,+ author, patchname_option, ask_long_comment,+ lookforadds,+ working_repo_dir]}++amendrecord_cmd :: [DarcsFlag] -> [String] -> IO ()+amendrecord_cmd opts args =+ let edit_metadata = has_edit_metadata opts in+ withRepoLock (testByDefault opts) $- \repository -> do+ files <- sort `fmap` fixSubPaths opts args+ when (areFileArgs files) $+ putStrLn $ "Amending changes in "++unwords (map show files)++":\n"+ with_selected_patch_from_repo "amend" repository opts $ \ (_ :> oldp) -> do+ ch <- if All `elem` opts + then get_unrecorded_in_files_unsorted repository (map sp2fn files)+ else get_unrecorded_in_files repository (map sp2fn files)+ case ch of+ NilFL | not edit_metadata -> putStrLn "No changes!"+ _ -> do+ date <- get_date opts+ with_selected_changes_to_files' "add" (filter (==All) opts)+ (map toFilePath files) ch $ \ (chs:>_) ->+ if (nullFL chs && not edit_metadata)+ then putStrLn "You don't want to record anything!"+ else do+ let old_pinf = info oldp+ prior = (pi_name old_pinf, pi_log old_pinf)+ make_log = world_readable_temp "darcs-amend-record"+ old_author = pi_author old_pinf+ author_here <- get_easy_author+ case author_here of+ Nothing -> return ()+ Just ah -> let edit_author = isJust (get_author opts)+ in if (edit_author || ah == old_author)+ then return ()+ else do yorn <- askUser $ "You're not "++old_author+ ++"! Amend anyway? "+ case yorn of ('y':_) -> return ()+ _ -> exitWith $ ExitSuccess+ (new_name, new_log, _) <- get_log opts (Just prior) make_log chs+ let new_author = case get_author opts of+ Just a -> a+ Nothing -> pi_author old_pinf+ maybe_invert = if is_inverted old_pinf then invert_name else id+ new_pinf <- maybe_invert `fmap` patchinfo date new_name+ new_author new_log+ let newp = fixp oldp chs new_pinf+ defineChanges newp+ invalidateIndex repository+ withGutsOf repository $ do+ tentativelyRemovePatches repository opts (hopefully oldp :>: NilFL)+ tentativelyAddPatch repository opts newp+ finalizeRepositoryChanges repository+ sync_repo repository+ putStrLn "Finished amending patch:"+ putDocLn $ description newp++has_edit_metadata :: [DarcsFlag] -> Bool+has_edit_metadata (Author _:_) = True+has_edit_metadata (LogFile _:_) = True+has_edit_metadata (PatchName _:_) = True+has_edit_metadata (EditLongComment:_) = True+has_edit_metadata (PromptLongComment:_) = True+has_edit_metadata (_:fs) = has_edit_metadata fs+has_edit_metadata [] = False++get_author :: [DarcsFlag] -> Maybe String+get_author (Author a:_) = Just a+get_author (_:as) = get_author as+get_author [] = Nothing++fixp :: RepoPatch p => PatchInfoAnd p -> FL Prim -> PatchInfo -> PatchInfoAnd p+fixp oldp chs new_pinf =+ let pdeps = getdeps $ hopefully oldp+ oldchs = effect oldp+ infodepspatch pinfo deps p = adddeps (infopatch pinfo p) deps+ in n2pia $ infodepspatch new_pinf pdeps $ fromPrims $ concatFL $ mapFL_FL canonize+ $ sort_coalesceFL $ concatFL $ mapFL_FL canonize $ oldchs +>+ chs+\end{code}
@@ -0,0 +1,411 @@+% Copyright (C) 2003 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\darcsCommand{annotate}+\begin{code}+{-# LANGUAGE CPP #-}+{-# OPTIONS_GHC -cpp #-}++#include "gadts.h"++module Darcs.Commands.Annotate ( annotate, created_as_xml ) where++import Control.Monad ( when )+import Data.List ( sort )++import Darcs.Commands ( DarcsCommand(..), nodefaults )+import Darcs.Arguments ( DarcsFlag(..), working_repo_dir,+ summary, unified, human_readable,+ xmloutput, creatorhash,+ fixSubPaths,+ list_registered_files,+ match_one,+ )+import Darcs.SlurpDirectory ( slurp )+import Darcs.Repository ( Repository, PatchSet, amInRepository, withRepository, ($-), read_repo,+ getMarkedupFile )+import Darcs.Patch ( RepoPatch, Named, LineMark(..), patch2patchinfo, xml_summary )+import qualified Darcs.Patch ( summary )+import Darcs.Ordered ( mapRL, concatRL )+import qualified Data.ByteString.Char8 as BC ( unpack, ByteString )+import Darcs.PrintPatch ( printPatch, contextualPrintPatch )+import Darcs.Patch.Info ( PatchInfo, human_friendly, to_xml, make_filename,+ showPatchInfo )+import Darcs.PopulationData ( Population(..), PopTree(..), DirMark(..),+ nameI, modifiedByI, modifiedHowI,+ createdByI, creationNameI,+ )+import Darcs.Population ( getRepoPopVersion, lookup_pop, lookup_creation_pop,+ modified_to_xml,+ )+import Darcs.Hopefully ( info )+import Darcs.RepoPath ( SubPath, toFilePath )+import Darcs.Match ( match_patch, have_nonrange_match, get_first_match )+import Darcs.Lock ( withTempDir )+import Darcs.Sealed ( Sealed2(..), unseal2 )+import Printer ( putDocLn, text, errorDoc, ($$), prefix, (<+>),+ Doc, empty, vcat, (<>), renderString, packedString )+#include "impossible.h"++annotate_description :: String+annotate_description = "Display which patch last modified something."++annotate_help :: String+annotate_help =+ "Annotate displays which patches created or last modified a directory\n"+++ "file or line. It can also display the contents of a particular patch\n"+++ "in darcs format.\n"++annotate :: DarcsCommand+annotate = DarcsCommand {command_name = "annotate",+ command_help = annotate_help,+ command_description = annotate_description,+ command_extra_args = -1,+ command_extra_arg_help = ["[FILE or DIRECTORY]..."],+ command_command = annotate_cmd,+ command_prereq = amInRepository,+ command_get_arg_possibilities = list_registered_files,+ command_argdefaults = nodefaults,+ command_advanced_options = [],+ command_basic_options = [summary,unified,+ human_readable,+ xmloutput,+ match_one, creatorhash,+ working_repo_dir]}+\end{code}++\begin{options}+--human-readable, --summary, --unified, --xml--output+\end{options}++When called with just a patch name, annotate outputs the patch in darcs format,+which is the same as \verb!--human-readable!.++\verb!--xml-output! is the alternative to \verb!--human-readable!.++\verb!--summary! can be used with either the \verb!--xml-output! or the +\verb!--human-readable! options to alter the results. It is documented+fully in the `common options' portion of the manual. ++Giving the \verb!--unified! flag implies \verb!--human-readable!, and causes+the output to remain in a darcs-specific format that is similar to that produced+by \verb!diff --unified!.+\begin{code}+annotate_cmd :: [DarcsFlag] -> [String] -> IO ()+annotate_cmd opts [] = withRepository opts $- \repository -> do+ when (not $ have_nonrange_match opts) $+ fail $ "Annotate requires either a patch pattern or a " +++ "file or directory argument."+ Sealed2 p <- match_patch opts `fmap` read_repo repository+ if Summary `elem` opts+ then do putDocLn $ showpi $ patch2patchinfo p+ putDocLn $ show_summary p+ else if Unified `elem` opts+ then withTempDir "context" $ \_ ->+ do get_first_match repository opts+ c <- slurp "."+ contextualPrintPatch c p+ else printPatch p+ where showpi | MachineReadable `elem` opts = showPatchInfo+ | XMLOutput `elem` opts = to_xml+ | otherwise = human_friendly+ show_summary :: RepoPatch p => Named p C(x y) -> Doc+ show_summary = if XMLOutput `elem` opts+ then xml_summary+ else Darcs.Patch.summary+\end{code}++If a directory name is given, annotate will output details of the last+modifying patch for each file in the directory and the directory itself. The+details look like this:++\begin{verbatim}+ # Created by [bounce handling patch+ # mark**20040526202216] as ./test/m7/bounce_handling.pl+ bounce_handling.pl+\end{verbatim}++If a patch name and a directory are given, these details are output for the time after+that patch was applied. If a directory and a tag name are given, the+details of the patches involved in the specified tagged version will be output.+\begin{code}+annotate_cmd opts args@[_] = withRepository opts $- \repository -> do+ r <- read_repo repository+ (rel_file_or_directory:_) <- fixSubPaths opts args+ let file_or_directory = rel_file_or_directory+ pinfo <- if have_nonrange_match opts+ then return $ patch2patchinfo `unseal2` (match_patch opts r)+ else case mapRL info $ concatRL r of+ [] -> fail "Annotate does not currently work correctly on empty repositories."+ (x:_) -> return x+ pop <- getRepoPopVersion "." pinfo++ -- deal with --creator-hash option+ let maybe_creation_pi = find_creation_patchinfo opts r+ lookup_thing = case maybe_creation_pi of+ Nothing -> lookup_pop+ Just cp -> lookup_creation_pop cp++ if toFilePath file_or_directory == ""+ then case pop of (Pop _ pt) -> annotate_pop opts pinfo pt+ else case lookup_thing (toFilePath file_or_directory) pop of+ Nothing -> fail $ "There is no file or directory named '"+++ toFilePath file_or_directory++"'"+ Just (Pop _ pt@(PopDir i _))+ | modifiedHowI i == RemovedDir && modifiedByI i /= pinfo ->+ errorDoc $ text ("The directory '" ++ toFilePath rel_file_or_directory +++ "' was removed by")+ $$ human_friendly (modifiedByI i)+ | otherwise -> annotate_pop opts pinfo pt+ Just (Pop _ pt@(PopFile i))+ | modifiedHowI i == RemovedFile && modifiedByI i /= pinfo ->+ errorDoc $ text ("The file '" ++ toFilePath rel_file_or_directory +++ "' was removed by")+ $$ human_friendly (modifiedByI i)+ | otherwise -> annotate_file repository opts pinfo file_or_directory pt++annotate_cmd _ _ = fail "annotate accepts at most one argument"++annotate_pop :: [DarcsFlag] -> PatchInfo -> PopTree -> IO ()+annotate_pop opts pinfo pt = putDocLn $ p2format pinfo pt+ where p2format = if XMLOutput `elem` opts+ then p2xml+ else p2s++indent :: Doc -> [Doc]+-- This is a bit nasty:+indent = map (text . i) . lines . renderString+ where i "" = ""+ i ('#':s) = ('#':s)+ i s = " "++s++-- Annotate a directory listing+p2s :: PatchInfo -> PopTree -> Doc+p2s pinfo (PopFile inf) =+ created_str+ $$ f <+> file_change+ where f = packedString $ nameI inf+ file_created = text "Created by"+ <+> showPatchInfo (fromJust $ createdByI inf)+ <+> text "as"+ <+> packedString (fromJust $ creationNameI inf)+ created_str = prefix "# " file_created+ file_change = if modifiedByI inf == pinfo+ then text $ show (modifiedHowI inf)+ else empty+p2s pinfo (PopDir inf pops) =+ created_str+ $$ dir <+> dir_change+ $$ vcat (map (vcat . indent . p2s pinfo) $ sort pops)+ where dir = packedString (nameI inf) <> text "/"+ dir_created =+ if createdByI inf /= Nothing+ then text "Created by "+ <+> showPatchInfo (fromJust $ createdByI inf)+ <+> text "as"+ <+> packedString (fromJust $ creationNameI inf) <> text "/"+ else text "Root directory"+ created_str = prefix "# " dir_created+ dir_change = if modifiedByI inf == pinfo+ then text $ show (modifiedHowI inf)+ else empty++escapeXML :: String -> Doc+escapeXML = text . strReplace '\'' "'" . strReplace '"' """ .+ strReplace '>' ">" . strReplace '<' "<" . strReplace '&' "&"++strReplace :: Char -> String -> String -> String+strReplace _ _ [] = []+strReplace x y (z:zs)+ | x == z = y ++ (strReplace x y zs)+ | otherwise = z : (strReplace x y zs)++created_as_xml :: PatchInfo -> String -> Doc+created_as_xml pinfo as = text "<created_as original_name='"+ <> escapeXML as+ <> text "'>"+ $$ to_xml pinfo+ $$ text "</created_as>"+--removed_by_xml :: PatchInfo -> String+--removed_by_xml pinfo = "<removed_by>\n"++to_xml pinfo++"</removed_by>\n"++p2xml_open :: PatchInfo -> PopTree -> Doc+p2xml_open _ (PopFile inf) =+ text "<file name='" <> escapeXML f <> text "'>"+ $$ created+ $$ modified+ where f = BC.unpack $ nameI inf+ created = case createdByI inf of+ Nothing -> empty+ Just ci -> created_as_xml ci+ (BC.unpack $ fromJust $ creationNameI inf)+ modified = modified_to_xml inf+p2xml_open _ (PopDir inf _) =+ text "<directory name='" <> escapeXML f <> text "'>"+ $$ created+ $$ modified+ where f = BC.unpack $ nameI inf+ created = case createdByI inf of+ Nothing -> empty+ Just ci -> created_as_xml ci+ (BC.unpack $ fromJust $ creationNameI inf)+ modified = modified_to_xml inf++p2xml_close :: PatchInfo -> PopTree -> Doc+p2xml_close _(PopFile _) = text "</file>"+p2xml_close _ (PopDir _ _) = text "</directory>"++p2xml :: PatchInfo -> PopTree -> Doc+p2xml pinf p@(PopFile _) = p2xml_open pinf p $$ p2xml_close pinf p+p2xml pinf p@(PopDir _ pops) = p2xml_open pinf p+ $$ vcat (map (p2xml pinf) $ sort pops)+ $$ p2xml_close pinf p+\end{code}++If a file name is given, the last modifying patch details of that file will be output, along+with markup indicating patch details when each line was last (and perhaps next) modified.++If a patch name and a file name are given, these details are output for the time after+that patch was applied.++\begin{code}+annotate_file :: RepoPatch p => Repository p C(r u t) -> [DarcsFlag] -> PatchInfo -> SubPath -> PopTree -> IO ()+annotate_file repository opts pinfo f (PopFile inf) = do+ if XMLOutput `elem` opts+ then putDocLn $ p2xml_open pinfo (PopFile inf)+ else if createdByI inf /= Nothing+ then putAnn $ text ("File "++toFilePath f++" created by ")+ <> showPatchInfo ci <> text (" as " ++ createdname)+ else putAnn $ text $ "File "++toFilePath f+ mk <- getMarkedupFile repository ci createdname+ old_pis <- (dropWhile (/= pinfo).mapRL info.concatRL) `fmap` read_repo repository+ mapM_ (annotate_markedup opts pinfo old_pis) mk+ when (XMLOutput `elem` opts) $ putDocLn $ p2xml_close pinfo (PopFile inf)+ where ci = fromJust $ createdByI inf+ createdname = BC.unpack $ fromJust $ creationNameI inf+annotate_file _ _ _ _ _ = impossible++annotate_markedup :: [DarcsFlag] -> PatchInfo -> [PatchInfo]+ -> (BC.ByteString, LineMark) -> IO ()+annotate_markedup opts | XMLOutput `elem` opts = xml_markedup+ | otherwise = text_markedup++text_markedup :: PatchInfo -> [PatchInfo] -> (BC.ByteString, LineMark) -> IO ()+text_markedup _ _ (l,None) = putLine ' ' l+text_markedup pinfo old_pis (l,RemovedLine wheni)+ | wheni == pinfo = putLine '-' l+ | wheni `elem` old_pis = return ()+ | otherwise = putLine ' ' l+text_markedup pinfo old_pis (l,AddedLine wheni)+ | wheni == pinfo = putLine '+' l+ | wheni `elem` old_pis = do putAnn $ text "Following line added by "+ <> showPatchInfo wheni+ putLine ' ' l+ | otherwise = return ()+text_markedup pinfo old_pis (l,AddedRemovedLine whenadd whenrem)+ | whenadd == pinfo = do putAnn $ text "Following line removed by "+ <> showPatchInfo whenrem+ putLine '+' l+ | whenrem == pinfo = do putAnn $ text "Following line added by "+ <> showPatchInfo whenadd+ putLine '-' l+ | whenadd `elem` old_pis && not (whenrem `elem` old_pis) =+ do putAnn $ text "Following line removed by " <> showPatchInfo whenrem+ putAnn $ text "Following line added by " <> showPatchInfo whenadd+ putLine ' ' l+ | otherwise = return ()++putLine :: Char -> BC.ByteString -> IO ()+putLine c s = putStrLn $ c : BC.unpack s+putAnn :: Doc -> IO ()+putAnn s = putDocLn $ prefix "# " s++xml_markedup :: PatchInfo -> [PatchInfo] -> (BC.ByteString, LineMark) -> IO ()+xml_markedup _ _ (l,None) = putLine ' ' l+xml_markedup pinfo old_pis (l,RemovedLine wheni)+ | wheni == pinfo = putDocLn $ text "<removed_line>"+ $$ escapeXML (BC.unpack l)+ $$ text "</removed_line>"+ | wheni `elem` old_pis = return ()+ | otherwise = putDocLn $ text "<normal_line>"+ $$ text "<removed_by>"+ $$ to_xml wheni+ $$ text "</removed_by>"+ $$ escapeXML (BC.unpack l)+ $$ text "</normal_line>"+xml_markedup pinfo old_pis (l,AddedLine wheni)+ | wheni == pinfo = putDocLn $ text "<added_line>"+ $$ escapeXML (BC.unpack l)+ $$ text "</added_line>"+ | wheni `elem` old_pis = putDocLn $ text "<normal_line>"+ $$ text "<added_by>"+ $$ to_xml wheni+ $$ text "</added_by>"+ $$ escapeXML (BC.unpack l)+ $$ text "</normal_line>"+ | otherwise = return ()+xml_markedup pinfo old_pis (l,AddedRemovedLine whenadd whenrem)+ | whenadd == pinfo =+ putDocLn $ text "<added_line>"+ $$ text "<removed_by>"+ $$ to_xml whenrem+ $$ text "</removed_by>"+ $$ escapeXML (BC.unpack l)+ $$ text "</added_line>"+ | whenrem == pinfo =+ putDocLn $ text "<removed_line>"+ $$ text "<added_by>"+ $$ to_xml whenadd+ $$ text "</added_by>"+ $$ escapeXML (BC.unpack l)+ $$ text "</removed_line>"+ | whenadd `elem` old_pis && not (whenrem `elem` old_pis) =+ putDocLn $ text "<normal_line>"+ $$ text "<removed_by>"+ $$ to_xml whenrem+ $$ text "</removed_by>"+ $$ text "<added_by>"+ $$ to_xml whenadd+ $$ text "</added_by>"+ $$ escapeXML (BC.unpack l)+ $$ text "</normal_line>"+ | otherwise = return ()+\end{code}++\begin{options}+--creator-hash HASH+\end{options}++The \verb!--creator-hash! option should only be used in combination with a+file or directory to be annotated. In this case, the name of that file or+directory is interpreted to be its name \emph{at the time it was created},+and the hash given along with \verb!--creator-hash! indicates the patch+that created the file or directory. This allows you to (relatively) easily+examine a file even if it has been renamed multiple times.++\begin{code}+find_creation_patchinfo :: [DarcsFlag] -> PatchSet p C(x) -> Maybe PatchInfo+find_creation_patchinfo [] _ = Nothing+find_creation_patchinfo (CreatorHash h:_) r = find_hash h $ mapRL info $ concatRL r+find_creation_patchinfo (_:fs) r = find_creation_patchinfo fs r++find_hash :: String -> [PatchInfo] -> Maybe PatchInfo+find_hash _ [] = Nothing+find_hash h (pinf:pinfs)+ | take (length h) (make_filename pinf) == h = Just pinf+ | otherwise = find_hash h pinfs+\end{code}
@@ -0,0 +1,431 @@+% Copyright (C) 2003-2005 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\darcsCommand{apply}+\begin{code}+{-# OPTIONS_GHC -cpp #-}+{-# LANGUAGE CPP #-}++module Darcs.Commands.Apply ( apply ) where+import System.Exit ( ExitCode(..), exitWith )+import Prelude hiding ( catch )+import System.IO ( hClose, stdin, stdout, stderr )+import Control.Exception ( catch, throw, Exception( ExitException ) )+import Control.Monad ( when )++import Darcs.Hopefully ( n2pia, conscientiously, info )+import Darcs.SignalHandler ( withSignalsBlocked )+import Darcs.Commands ( DarcsCommand(..) )+import Darcs.CommandsAux ( check_paths )+import Darcs.Arguments ( DarcsFlag( Reply, Interactive, All,+ Verbose, HappyForwarding ),+ definePatches,+ get_cc, working_repo_dir,+ notest, nocompress, apply_conflict_options,+ use_external_merge,+ ignoretimes, get_sendmail_cmd,+ reply, verify, list_files,+ fixFilePathOrStd, umask_option,+ all_interactive, sendmail_cmd,+ leave_test_dir, happy_forwarding, + dry_run, print_dry_run_message_and_exit,+ set_scripts_executable, restrict_paths+ )+import qualified Darcs.Arguments as DarcsArguments ( cc )+import Darcs.RepoPath ( toFilePath, useAbsoluteOrStd )+import Darcs.Repository ( SealedPatchSet, withRepoLock, ($-), amInRepository,+ tentativelyMergePatches,+ sync_repo, read_repo,+ finalizeRepositoryChanges,+ applyToWorking,+ )+import Darcs.Patch ( RepoPatch, description )+import Darcs.Patch.Info ( human_friendly )+import Darcs.Ordered ( (:\/:)(..), (:>)(..), unsafeUnRL,+ mapFL, nullFL, mapFL_FL, mapRL, concatRL, reverseRL )+import Darcs.SlurpDirectory ( wait_a_moment )++import ByteStringUtils ( linesPS, unlinesPS )+import qualified Data.ByteString as B (ByteString, null, readFile, hGetContents, init, take, drop)+import qualified Data.ByteString.Char8 as BC (unpack, last, pack)++import Darcs.External ( sendEmail, sendEmailDoc, resendEmail,+ verifyPS )+import Darcs.Email ( read_email )+import Darcs.Lock ( withStdoutTemp, readBinFile )+import Darcs.Patch.Depends ( get_common_and_uncommon_or_missing )+import Darcs.SelectChanges ( with_selected_changes )+import Darcs.Patch.Bundle ( scan_bundle )+import Darcs.Sealed ( Sealed(Sealed) )+import Printer ( packedString, putDocLn, vcat, text, ($$), errorDoc, empty )+import Darcs.Gorsvet( invalidateIndex )+#include "impossible.h"++apply_description :: String+apply_description = "Apply a patch bundle created by `darcs send'."++apply_help :: String+apply_help =+ "Apply is used to apply a bundle of patches to this repository.\n"+++ "Such a bundle may be created using send.\n"++stdindefault :: a -> [String] -> IO [String]+stdindefault _ [] = return ["-"]+stdindefault _ x = return x+apply :: DarcsCommand+apply = DarcsCommand {command_name = "apply",+ command_help = apply_help,+ command_description = apply_description,+ command_extra_args = 1,+ command_extra_arg_help = ["<PATCHFILE>"],+ command_command = apply_cmd,+ command_prereq = amInRepository,+ command_get_arg_possibilities = list_files,+ command_argdefaults = const stdindefault,+ command_advanced_options = [reply, DarcsArguments.cc,+ happy_forwarding,+ sendmail_cmd,+ ignoretimes, nocompress,+ set_scripts_executable, umask_option,+ restrict_paths],+ command_basic_options = [verify,+ all_interactive]++dry_run+++ [apply_conflict_options,+ use_external_merge,+ notest,+ leave_test_dir,+ working_repo_dir]}++apply_cmd :: [DarcsFlag] -> [String] -> IO ()+apply_cmd _ [""] = fail "Empty filename argument given to apply!"+apply_cmd opts [unfixed_patchesfile] = withRepoLock opts $- \repository -> do+ patchesfile <- fixFilePathOrStd opts unfixed_patchesfile+ ps <- useAbsoluteOrStd (B.readFile . toFilePath) (B.hGetContents stdin) patchesfile+ am_verbose <- return $ Verbose `elem` opts+ let from_whom = get_from ps+ us <- read_repo repository+ either_them <- get_patch_bundle opts ps+ them <- case either_them of+ Right (Sealed t) -> return t+ Left er -> do forwarded <- consider_forwarding opts ps+ if forwarded+ then exitWith ExitSuccess+ else fail er+ (_, us':\/:them') <- case get_common_and_uncommon_or_missing (us, them) of+ Left pinfo ->+ if pinfo `elem` mapRL info (concatRL us)+ then cannotApplyPartialRepo pinfo ""+ else cannotApplyMissing pinfo+ Right x -> return x+ when (null $ unsafeUnRL $ head $ unsafeUnRL them') $+ do putStr $ "All these patches have already been applied. " +++ "Nothing to do.\n"+ exitWith ExitSuccess+ let their_ps = mapFL_FL (n2pia . conscientiously (text ("We cannot apply this patch "+ ++"bundle, since we're missing:") $$))+ $ reverseRL $ head $ unsafeUnRL them'+ with_selected_changes "apply" fixed_opts their_ps $+ \ (to_be_applied:>_) -> do+ print_dry_run_message_and_exit "apply" opts to_be_applied+ when (nullFL to_be_applied) $+ do putStrLn "You don't want to apply any patches, so I'm exiting!"+ exitWith ExitSuccess+ check_paths opts to_be_applied+ redirect_output opts from_whom $ do+ when am_verbose $ putStrLn "We have the following extra patches:"+ when am_verbose $ putDocLn $ vcat $ mapRL description $ head $ unsafeUnRL us'+ when am_verbose $ putStrLn "Will apply the following patches:"+ when am_verbose $ putDocLn $ vcat $ mapFL description to_be_applied+ definePatches to_be_applied+ Sealed pw <- tentativelyMergePatches repository "apply" opts+ (reverseRL $ head $ unsafeUnRL us') to_be_applied+ invalidateIndex repository+ withSignalsBlocked $ do finalizeRepositoryChanges repository+ wait_a_moment -- so work will be more recent than rec+ applyToWorking repository opts pw `catch` \e ->+ fail ("Error applying patch to working dir:\n" ++ show e)+ sync_repo repository+ putStrLn "Finished applying..."+ where fixed_opts = if Interactive `elem` opts+ then opts+ else All : opts+ cannotApplyMissing pinfo+ = errorDoc $ text "Cannot apply this patch bundle, since we're missing:"+ $$ human_friendly pinfo+ cannotApplyPartialRepo pinfo e+ = errorDoc $ text ("Cannot apply this patch bundle, "+ ++ "this is a \"--partial repository")+ $$ text "We don't have the following patch:"+ $$ human_friendly pinfo $$ text e+apply_cmd _ _ = impossible+\end{code}++Darcs apply accepts a single argument, which is the name of the patch+file to be applied. If you omit this argument, the patch is read from+standard input. Darcs also interprets an argument of `\-' to mean it+should read the file from standard input. This allows you to use apply+with a pipe from your email program, for example.++\begin{options}+--verify+\end{options}++If you specify the \verb!--verify PUBRING! option, darcs will check that+the patch was GPG-signed by a key which is in \verb!PUBRING! and will+refuse to apply the patch otherwise.++\begin{code}+get_patch_bundle :: RepoPatch p => [DarcsFlag] -> B.ByteString+ -> IO (Either String (SealedPatchSet p))+get_patch_bundle opts fps = do+ mps <- verifyPS opts $ read_email fps+ mops <- verifyPS opts fps+ case (mps, mops) of+ (Nothing, Nothing) ->+ return $ Left "Patch bundle not properly signed, or gpg failed."+ (Just ps, Nothing) -> return $ scan_bundle ps+ (Nothing, Just ps) -> return $ scan_bundle ps+ -- We use careful_scan_bundle only below because in either of the two+ -- above case we know the patch was signed, so it really shouldn't+ -- need stripping of CRs.+ (Just ps1, Just ps2) -> case careful_scan_bundle ps1 of+ Left _ -> return $ careful_scan_bundle ps2+ Right x -> return $ Right x+ where careful_scan_bundle ps =+ case scan_bundle ps of+ Left e -> case scan_bundle $ stripCrPS ps of+ Right x -> Right x+ _ -> Left e+ x -> x+ stripCrPS :: B.ByteString -> B.ByteString+ stripCrPS ps = unlinesPS $ map stripline $ linesPS ps+ stripline p | B.null p = p+ | BC.last p == '\r' = B.init p+ | otherwise = p+\end{code}++\begin{options}+--cc, --reply+\end{options}++If you give the \verb!--reply FROM! option to \verb!darcs apply!, it will send the+results of the application to the sender of the patch. This only works if+the patch is in the form of email with its headers intact, so that darcs+can actually know the origin of the patch. The reply email will indicate+whether or not the patch was successfully applied. The \verb!FROM! flag is+the email address that will be used as the ``from'' address when replying.+If the darcs apply is being done automatically, it is important that this+address not be the same as the address at which the patch was received, in+order to avoid automatic email loops.++If you want to also send the apply email to another address (for example,+to create something like a ``commits'' mailing list), you can use the+\verb!--cc! option to specify additional recipients. Note that the+\verb!--cc! option \emph{requires} the \verb!--reply! option, which+provides the ``From'' address.++The \verb!--reply! feature of apply is intended primarily for two uses.+When used by itself, it is handy for when you want to apply patches sent to+you by other developers so that they will know when their patch has been+applied. For example, in my \verb!.muttrc! (the config file for my mailer)+I have:+\begin{verbatim}+macro pager A "<pipe-entry>darcs apply --verbose \+ --reply droundy@abridgegame.org --repodir ~/darcs+\end{verbatim}+which allows me to apply a patch to darcs directly from my mailer, with the+originator of that patch being sent a confirmation when the patch is+successfully applied. NOTE: In an attempt to make sure no one else+can read your email, mutt seems to set the umask+such that patches created with the above macro are not world-readable, so+use it with care.++When used in combination with the \verb!--verify! option, the+\verb!--reply! option allows for a nice pushable repository. When these+two options are used together, any patches that don't pass the verify will+be forwarded to the \verb!FROM! address of the \verb!--reply! option. This+allows you to set up a repository so that anyone who is authorized can push+to it and have it automatically applied, but if a stranger pushes to it,+the patch will be forwarded to you. Please (for your own sake!)\ be certain+that the \verb!--reply FROM! address is different from the one used to send+patches to a pushable repository, since otherwise an unsigned patch will be+forwarded to the repository in an infinite loop.++If you use \verb!darcs apply --verify PUBRING --reply! to create a+pushable repository by applying patches automatically as they are received by+email, you will also want to use the \verb!--dont-allow-conflicts! option.++\begin{options}+--dont-allow-conflicts+\end{options}+The \verb!--dont-allow-conflicts! flag causes apply to fail when applying a+patch would cause conflicts. This flag is recommended on repositories+which will be pushed to or sent to.++\begin{options}+--allow-conflicts+\end{options}++\verb!--allow-conflicts! will allow conflicts, but will keep the local and+recorded versions in sync on the repository. This means the conflict will exist+in both locations until it is resolved.++\begin{options}+--mark-conflicts+\end{options}++\verb!--mark-conflicts! will add conflict markers to illustrate the the+conflict.++\begin{options}+--external-merge+\end{options}++You can use an external interactive merge tool to resolve conflicts with the+flag \verb!--external-merge!. For more details see+subsection~\ref{resolution}.++\begin{options}+--all, --interactive+\end{options}++If you provide the \verb!--interactive! flag, darcs will+ask you for each change in the patch bundle whether or not you wish to+apply that change. The opposite is the \verb!--all! flag, which can be+used to override an \verb!interactive! which might be set in your+``defaults'' file.++\begin{options}+--sendmail-command+\end{options}++If you want to use a command different from the default one for sending mail,+you need to specify a command line with the \verb!--sendmail-command! option.+The command line can contain the format specifier \verb!%t! for to+and you can add \verb!%<! to the end of the command line if the command+expects the complete mail on standard input. For example, the command line for+msmtp looks like this:++\begin{verbatim}+msmtp -t %<+\end{verbatim}+++\begin{code}+get_from :: B.ByteString -> String+get_from ps = readFrom $ linesPS ps+ where readFrom [] = ""+ readFrom (x:xs)+ | B.take 5 x == from_start = BC.unpack $ B.drop 5 x+ | otherwise = readFrom xs++redirect_output :: [DarcsFlag] -> String -> IO a -> IO a+redirect_output opts to doit = ro opts+ where+ cc = get_cc opts+ ro [] = doit+ ro (Reply f:_) =+ withStdoutTemp $ \tempf-> do {a <- doit;+ hClose stdout;+ hClose stderr;+ return a;+ } `catch` (sendit tempf)+ where sendit tempf e@(ExitException ExitSuccess) =+ do sendSanitizedEmail opts f to "Patch applied" cc tempf+ throwIO e+ sendit tempf (ExitException _) =+ do sendSanitizedEmail opts f to "Patch failed!" cc tempf+ throwIO $ ExitException ExitSuccess+ sendit tempf e =+ do sendSanitizedEmail opts f to "Darcs error applying patch!" cc $+ tempf ++ "\n\nCaught exception:\n"+++ show e++"\n"+ throwIO $ ExitException ExitSuccess+ ro (_:fs) = ro fs++-- |sendSanitizedEmail sends a sanitized email using the given sendmailcmd+-- It takes @DacrsFlag@ options a file with the mail contents,+-- To:, Subject:, CC:, and mail body+sendSanitizedEmail :: [DarcsFlag] -> String -> String -> String -> String -> String -> IO ()+sendSanitizedEmail opts file to subject cc mailtext =+ do scmd <- get_sendmail_cmd opts+ body <- sanitizeFile mailtext+ sendEmail file to subject cc scmd body++-- sanitizeFile is used to clean up the stdout/stderr before sticking it in+-- an email.++sanitizeFile :: FilePath -> IO String+sanitizeFile f = sanitize `fmap` readBinFile f+ where sanitize s = wash $ remove_backspaces "" s+ wash ('\000':s) = "\\NUL" ++ wash s+ wash ('\026':s) = "\\EOF" ++ wash s+ wash (c:cs) = c : wash cs+ wash [] = []+ remove_backspaces rev_sofar "" = reverse rev_sofar+ remove_backspaces (_:rs) ('\008':s) = remove_backspaces rs s+ remove_backspaces "" ('\008':s) = remove_backspaces "" s+ remove_backspaces rs (s:ss) = remove_backspaces (s:rs) ss++throwIO :: Exception -> IO a+throwIO e = return $ throw e++forwarding_message :: B.ByteString+forwarding_message = BC.pack $+ "The following patch was either unsigned, or signed by a non-allowed\n"+++ "key, or there was a GPG failure.\n"++consider_forwarding :: [DarcsFlag] -> B.ByteString -> IO Bool+consider_forwarding opts m = cf opts (get_cc opts)+ where cf [] _ = return False+ cf (Reply t:_) cc =+ case break is_from (linesPS m) of+ (m1, f:m2) ->+ let m_lines = forwarding_message:m1 ++ m2+ m' = unlinesPS m_lines+ f' = BC.unpack (B.drop 5 f) in+ if t == f' || t == init f'+ then return False -- Refuse possible email loop.+ else do+ scmd <- get_sendmail_cmd opts+ if HappyForwarding `elem` opts+ then resendEmail t scmd m+ else sendEmailDoc f' t "A forwarded darcs patch" cc+ scmd (Just (empty,empty))+ (packedString m')+ return True+ _ -> return False -- Don't forward emails lacking headers!+ cf (_:fs) cc = cf fs cc+ is_from l = B.take 5 l == from_start++from_start :: B.ByteString+from_start = BC.pack "From:"+\end{code}++\begin{options}+--no-test, --test+\end{options}++If you specify the \verb!--test! option, apply will run the test (if a test+exists) prior to applying the patch. If the test fails, the patch is not+applied. In this case, if the \verb!--reply! option was used, the results+of the test are sent in the reply email. You can also specify the+\verb!--no-test! option, which will override the \verb!--test! option, and+prevent the test from being run. This is helpful when setting up a+pushable repository, to keep users from running code.++
@@ -0,0 +1,294 @@+% Copyright (C) 2003-2004 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\darcsCommand{changes}+\begin{code}+{-# OPTIONS_GHC -cpp -fglasgow-exts #-}+{-# LANGUAGE CPP, PatternGuards #-}++module Darcs.Commands.Changes ( changes ) where++import Data.List ( intersect, sort )+import Data.Maybe ( fromMaybe )+import Control.Monad ( when, unless )++import Darcs.Hopefully ( hopefullyM, info )+import Darcs.Patch.Depends ( slightly_optimize_patchset )+import Darcs.Commands ( DarcsCommand(..), nodefaults )+import Darcs.Arguments ( DarcsFlag(Context, HumanReadable, MachineReadable,+ Interactive, OnlyChangesToFiles, Count,+ NumberPatches, XMLOutput, Summary,+ Reverse, Verbose, Debug),+ fixSubPaths, changes_format,+ possibly_remote_repo_dir, get_repourl,+ working_repo_dir, only_to_files,+ summary, changes_reverse,+ match_several_or_range,+ match_maxcount, maxCount,+ all_interactive, showFriendly,+ network_options+ )+import Darcs.RepoPath ( toFilePath, rootDirectory )+import Darcs.Patch.FileName ( fp2fn, fn2fp, norm_path )+import Darcs.Repository ( Repository, PatchSet, PatchInfoAnd,+ get_unrecorded_in_files_unsorted,+ withRepositoryDirectory, ($-), findRepository,+ read_repo )+import Darcs.Patch.Info ( to_xml, showPatchInfo )+import Darcs.Patch.Depends ( get_common_and_uncommon )+import Darcs.Patch.TouchesFiles ( look_touch )+import Darcs.Patch ( RepoPatch, invert, xml_summary, description, apply_to_filepaths,+ list_touched_files, effect, identity )+import Darcs.Ordered ( (:\/:)(..), RL(..), unsafeFL, unsafeUnRL, concatRL,+ EqCheck(..), filterFL )+import Darcs.Match ( first_match, second_match,+ match_a_patchread, have_nonrange_match,+ match_first_patchset, match_second_patchset,+ )+import Darcs.Commands.Annotate ( created_as_xml )+import Printer ( Doc, putDocLnWith, simplePrinters, (<+>),+ renderString, prefix, text, vcat, vsep, + ($$), empty, errorDoc, insert_before_lastline )+import Darcs.ColorPrinter ( fancyPrinters )+import Progress ( setProgressMode, debugMessage )+import Darcs.SelectChanges ( view_changes )+import Darcs.Sealed ( unsafeUnseal )+#include "impossible.h"++changes_description :: String+changes_description = "Gives a changelog-style summary of the repository history."++changes_help :: String+changes_help =+ "Changes gives a changelog-style summary of the repository history,\n"+++ "including options for altering how the patches are selected and displayed.\n"++changes :: DarcsCommand+changes = DarcsCommand {command_name = "changes",+ command_help = changes_help,+ command_description = changes_description,+ command_extra_args = -1,+ command_extra_arg_help = ["[FILE or DIRECTORY]..."],+ command_get_arg_possibilities = return [],+ command_command = changes_cmd,+ command_prereq = findRepository,+ command_argdefaults = nodefaults,+ command_advanced_options = network_options,+ command_basic_options = [match_several_or_range,+ match_maxcount,+ only_to_files,+ changes_format,+ summary,+ changes_reverse,+ possibly_remote_repo_dir,+ working_repo_dir,+ all_interactive]}++changes_cmd :: [DarcsFlag] -> [String] -> IO ()+changes_cmd [Context _] [] = return ()+changes_cmd opts args | Context rootDirectory `elem` opts =+ let repodir = fromMaybe "." (get_repourl opts) in+ withRepositoryDirectory opts repodir $- \repository -> do+ when (args /= []) $ fail "changes --context cannot accept other arguments"+ changes_context repository opts+changes_cmd opts args =+ let repodir = fromMaybe "." (get_repourl opts) in+ withRepositoryDirectory opts repodir $- \repository -> do+ unless (Debug `elem` opts) $ setProgressMode False+ files <- sort `fmap` fixSubPaths opts args+ unrec <- if null files then return identity+ else get_unrecorded_in_files_unsorted repository (map (fp2fn . toFilePath) files)+ `catch` \_ -> return identity -- this is triggered when repository is remote+ let filez = map (fn2fp . norm_path . fp2fn) $ apply_to_filepaths (invert unrec) $ map toFilePath files+ filtered_changes p = maybe_reverse $ get_changes_info opts filez p+ debugMessage "About to read the repository..."+ patches <- read_repo repository+ debugMessage "Done reading the repository."+ if Interactive `elem` opts+ then do let (fp_and_fs, _, _) = filtered_changes patches+ fp = map fst fp_and_fs+ view_changes opts (unsafeFL fp)+ else do when (not (null files) && not (XMLOutput `elem` opts)) $+ putStrLn $ "Changes to "++unwords filez++":\n"+ debugMessage "About to print the changes..."+ let printers = if XMLOutput `elem` opts then simplePrinters else fancyPrinters+ ps <- read_repo repository -- read repo again to prevent holding onto+ -- values forced by filtered_changes+ putDocLnWith printers $ changelog opts ps $ filtered_changes patches+ where maybe_reverse (xs,b,c) = if Reverse `elem` opts+ then (reverse xs, b, c)+ else (xs, b, c)+\end{code}++When given one or more files or directories as an argument, changes lists only+those patches which affect those files or the contents of those directories or,+of course, the directories themselves. This includes changes that happened to+files before they were moved or renamed.++\begin{options}+--from-match, --from-patch, --from-tag+\end{options}++If changes is given a \verb!--from-patch!, \verb!--from-match!, or+\verb!--from-tag! option, it outputs only those changes since that tag or+patch.++Without any options to limit the scope of the changes, history will be displayed+going back as far as possible.++\begin{options}+--max-count+\end{options}++If changes is given a \verb!--max-count! option, it only outputs up to as that+number of changes.++\begin{code}+get_changes_info :: RepoPatch p => [DarcsFlag] -> [FilePath] -> PatchSet p+ -> ([(PatchInfoAnd p, [FilePath])], [FilePath], Doc)+get_changes_info opts plain_fs ps =+ case get_common_and_uncommon (p2s,p1s) of+ (_,us:\/:_) -> filter_patches_by_names (maxCount opts) fs $ filter pf $ unsafeUnRL $ concatRL us+ where fs = map (\x -> "./" ++ x) $ plain_fs+ p1s = if first_match opts then unsafeUnseal $ match_first_patchset opts ps+ else NilRL:<:NilRL+ p2s = if second_match opts then unsafeUnseal $ match_second_patchset opts ps+ else ps+ pf = if have_nonrange_match opts+ then match_a_patchread opts+ else \_ -> True++-- | Take a list of filenames and patches and produce a list of+-- patches that actually touch the given files with list of touched+-- file names, a new file list that represents the same set of files+-- as in input, before the returned patches would have been applied,+-- and possibly an error. Additionaly, the function takes a "depth+-- limit" -- maxcount, that could be Nothing (return everything) or+-- "Just n" -- returns at most n patches touching the file (starting+-- from the beginning of the patch list).+filter_patches_by_names :: RepoPatch p =>+ Maybe Int -- ^ maxcount+ -> [FilePath] -- ^ filenames+ -> [PatchInfoAnd p] -- ^ patchlist+ -> ([(PatchInfoAnd p,[FilePath])], [FilePath], Doc)+filter_patches_by_names (Just 0) _ _ = ([], [], empty)+filter_patches_by_names _ _ [] = ([], [], empty)+filter_patches_by_names maxcount [] (hp:ps) =+ (hp, []) -:- filter_patches_by_names (subtract 1 `fmap` maxcount) [] ps+filter_patches_by_names maxcount fs (hp:ps)+ | Just p <- hopefullyM hp =+ case look_touch fs (invert p) of+ (True, []) -> ([(hp, fs)], fs, empty)+ (True, fs') -> (hp, fs) -:- filter_patches_by_names+ (subtract 1 `fmap` maxcount) fs' ps+ (False, fs') -> filter_patches_by_names maxcount fs' ps+filter_patches_by_names _ _ (hp:_) =+ ([], [], text "Can't find changes prior to:" $$ description hp)++-- | Note, lazy pattern matching is required to make functions like+-- filter_patches_by_names lazy in case you are only not interested in+-- the first element. E.g.:+--+-- let (fs, _, _) = filter_patches_by_names ...+(-:-) :: a -> ([a],b,c) -> ([a],b,c)+x -:- ~(xs,y,z) = (x:xs,y,z)++changelog :: RepoPatch p => [DarcsFlag] -> PatchSet p -> ([(PatchInfoAnd p, [FilePath])], [FilePath], Doc)+ -> Doc+changelog opts patchset (pis_and_fs, orig_fs, errstring)+ | Count `elem` opts = text $ show $ length pis_and_fs+ | MachineReadable `elem` opts =+ if renderString errstring == ""+ then vsep $ map (showPatchInfo.info) pis+ else errorDoc errstring+ | XMLOutput `elem` opts =+ text "<changelog>"+ $$ vcat xml_file_names+ $$ vcat actual_xml_changes+ $$ text "</changelog>"+ | Summary `elem` opts || Verbose `elem` opts =+ vsep (map (number_patch change_with_summary) pis_and_fs)+ $$ errstring+ | otherwise = vsep (map (number_patch description') pis_and_fs)+ $$ errstring+ where change_with_summary (hp, fs)+ | Just p <- hopefullyM hp = if OnlyChangesToFiles `elem` opts+ then description hp $$ text "" $$+ indent (showFriendly opts (filterFL xx $ effect p))+ else showFriendly opts p+ | otherwise = description hp+ $$ indent (text "[this patch is unavailable]")+ where xx x = case list_touched_files x of+ ys | null $ ys `intersect` fs -> IsEq+ _ -> NotEq+ xml_with_summary hp+ | Just p <- hopefullyM hp = insert_before_lastline+ (to_xml $ info hp) (indent $ xml_summary p)+ xml_with_summary hp = to_xml (info hp)+ indent = prefix " "+ actual_xml_changes = if Summary `elem` opts+ then map xml_with_summary pis+ else map (to_xml.info) pis+ xml_file_names = map (created_as_xml first_change) orig_fs+ first_change = if Reverse `elem` opts+ then info $ head pis+ else info $ last pis+ number_patch f x = if NumberPatches `elem` opts+ then case get_number (fst x) of+ Just n -> text (show n++":") <+> f x+ Nothing -> f x+ else f x+ get_number :: PatchInfoAnd p -> Maybe Int+ get_number y = gn 1 (concatRL patchset)+ where iy = info y+ gn n (b:<:bs) | seq n (info b) == iy = Just n+ | otherwise = gn (n+1) bs+ gn _ NilRL = Nothing+ pis = map fst pis_and_fs+ description' = description . fst++\end{code}++\begin{options}+--context, --human-readable, --xml-output+\end{options}++When given the \verb!--context! flag, darcs changes outputs sufficient+information to allow the current state of the repository to be+recreated at a later date. This information should generally be piped to a+file, and then can be used later in conjunction with+\verb!darcs get --context! to recreate the current version. Note that+while the \verb!--context! flag may be used in conjunction with+\verb!--xml-output! or \verb!--human-readable!, in neither case will darcs+get be able to read the output. On the other hand, sufficient information+\emph{will} be output for a knowledgeable human to recreate the current+state of the repository.+\begin{code}+changes_context :: RepoPatch p => Repository p -> [DarcsFlag] -> IO ()+changes_context repository opts = do+ r <- read_repo repository+ putStrLn "\nContext:\n"+ when (not $ null (unsafeUnRL r) || null (unsafeUnRL $ head $ unsafeUnRL r)) $+ putDocLnWith simplePrinters $ changelog opts' NilRL $+ get_changes_info opts' []+ (headRL (slightly_optimize_patchset r) :<: NilRL)+ where opts' = if HumanReadable `elem` opts || XMLOutput `elem` opts+ then opts+ else MachineReadable : opts+ headRL (x:<:_) = x+ headRL NilRL = impossible+\end{code}
@@ -0,0 +1,107 @@+% Copyright (C) 2002-2005 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\darcsCommand{check}+\begin{code}+module Darcs.Commands.Check ( check ) where+import Control.Monad ( when )+import System.Exit ( ExitCode(..), exitWith )++import Darcs.Commands ( DarcsCommand(..), nodefaults )+import Darcs.Arguments ( DarcsFlag( Quiet ),+ partial_check, notest, testByDefault,+ leave_test_dir, working_repo_dir,+ )+import Darcs.Repository.Repair( replayRepository,+ RepositoryConsistency(..) )+import Darcs.Repository ( Repository, amInRepository, withRepository, slurp_recorded,+ testRecorded )+import Darcs.Patch ( RepoPatch, showPatch )+import Darcs.Ordered ( FL(..) )+import Darcs.Diff ( unsafeDiff )+import Darcs.Repository.Prefs ( filetype_function )+import Printer ( putDocLn, text, ($$), (<+>) )++check_description :: String+check_description = "Check the repository for consistency."++check_help :: String+check_help =+ "This command verifies that the patches in the repository, when applied\n" +++ "successively to an empty tree, result in the pristine tree. If not,\n" +++ "the differences are printed and Darcs exits unsucessfully (with a\n" +++ "non-zero exit status).\n" +++ "\n" +++ "If the repository is in darcs-1 format and has a checkpoint, you can\n" +++ "use the --partial option to start checking from the latest checkpoint.\n" +++ "This is the default for partial darcs-1 repositories; the --complete\n" +++ "option to forces a full check.\n" +++ "\n" +++ "If a regression test is defined (see `darcs setpref') it will be run\n" +++ "by `darcs check'. Use the --no-test option to disable this.\n"++check :: DarcsCommand+check = DarcsCommand {command_name = "check",+ command_help = check_help,+ command_description = check_description,+ command_extra_args = 0,+ command_extra_arg_help = [],+ command_command = check_cmd,+ command_prereq = amInRepository,+ command_get_arg_possibilities = return [],+ command_argdefaults = nodefaults,+ command_advanced_options = [],+ command_basic_options = [partial_check,+ notest,+ leave_test_dir,+ working_repo_dir+ ]}++check_cmd :: [DarcsFlag] -> [String] -> IO ()+check_cmd opts _ = withRepository opts (check' opts)++check' :: (RepoPatch p) => [DarcsFlag] -> Repository p -> IO ()+check' opts repository = do+ replayRepository repository (testByDefault opts) $ \ state -> do+ case state of+ RepositoryConsistent -> do+ putInfo $ text "The repository is consistent!"+ testRecorded repository+ exitWith ExitSuccess+ BrokenPristine newpris -> do+ brokenPristine newpris+ exitWith $ ExitFailure 1+ BrokenPatches newpris _ -> do+ brokenPristine newpris+ putInfo $ text "Found broken patches."+ exitWith $ ExitFailure 1+ where + brokenPristine newpris = do+ putInfo $ text "Looks like we have a difference..."+ mc <- slurp_recorded repository+ ftf <- filetype_function+ putInfo $ case unsafeDiff opts ftf newpris mc of+ NilFL -> text "Nothing"+ patch -> text "Difference: " <+> showPatch patch+ putInfo $ text ""+ $$ text "Inconsistent repository!"+ putInfo s = when (not $ Quiet `elem` opts) $ putDocLn s++\end{code}+%% FIXME: this should go in "common options" or something, since+%% commands like record and amend-record also run the test command.+\input{Darcs/Test.lhs}
@@ -0,0 +1,260 @@+% Copyright (C) 2002-2005,2007 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\darcsCommand{convert}+\begin{code}+{-# OPTIONS_GHC -cpp -fglasgow-exts #-}+{-# LANGUAGE CPP #-}+-- , MagicHash #-}++#include "gadts.h"++module Darcs.Commands.Convert ( convert ) where++import System.Directory ( setCurrentDirectory, doesDirectoryExist, doesFileExist,+ createDirectory )+import Workaround ( getCurrentDirectory )+import Control.Monad ( when )+import GHC.Base ( unsafeCoerce# )+import Data.Maybe ( catMaybes )++import Darcs.Hopefully ( PatchInfoAnd, n2pia, info, hopefully )+import Darcs.Commands ( DarcsCommand(..), nodefaults )+import Darcs.Arguments ( DarcsFlag( AllowConflicts, NewRepo,+ SetScriptsExecutable, UseFormat2, NoUpdateWorking,+ Verbose, Quiet ),+ reponame,+ set_scripts_executable,+ network_options )+import Darcs.Repository ( Repository, withRepoLock, ($-), withRepositoryDirectory, read_repo,+ createRepository,+ slurp_recorded, optimizeInventory,+ tentativelyMergePatches, patchSetToPatches,+ createPristineDirectoryTree,+ revertRepositoryChanges, finalizeRepositoryChanges, sync_repo )+import Darcs.Global ( darcsdir )+import Darcs.Patch ( RealPatch, Patch, Named, showPatch, patch2patchinfo, fromPrims, infopatch,+ modernize_patch,+ adddeps, getdeps, effect, flattenFL, is_merger, patchcontents )+import Darcs.Ordered ( FL(..), RL(..), EqCheck(..), (=/\=), bunchFL, mapFL, mapFL_FL,+ concatFL, concatRL, mapRL )+import Darcs.Patch.Info ( pi_rename, pi_tag, is_tag )+import Darcs.Patch.Commute ( public_unravel )+import Darcs.Patch.Real ( mergeUnravelled )+import Darcs.RepoPath ( ioAbsoluteOrRemote, toPath )+import Darcs.Repository.Motd ( show_motd )+import Darcs.Utils ( clarify_errors, askUser )+import Darcs.ProgressPatches ( progressFL )+import Darcs.Sealed ( FlippedSeal(..) )+import Printer ( text, putDocLn, ($$) )+import Darcs.ColorPrinter ( traceDoc )+import Darcs.SlurpDirectory ( list_slurpy_files )+import Darcs.Lock ( writeBinFile )+import Workaround ( setExecutable )+import qualified Data.ByteString as B (isPrefixOf, readFile)+import qualified Data.ByteString.Char8 as BC (pack)+import Darcs.Gorsvet( invalidateIndex )++convert_description :: String+convert_description = "Convert a repository from a legacy format."++convert_help :: String+convert_help =+ "The current repository format is called `darcs-2'. It was introduced\n" +++ "in Darcs 2.0 and became the default for new projects in Darcs 2.2.\n" +++ "The `darcs convert' command allows existing projects to migrate to\n" +++ "this format from the older `darcs-1' format.\n" +++ "\n" +++ "This command DOES NOT modify the source repository; a new destination\n" +++ "repository is created. It is safe to run this command more than once\n" +++ "on a repository (e.g. for testing), before the final conversion.\n" +++ "\n" +++ convert_help' +++ "\n" +++ "Due to this limitation, migrating a multi-branch project is a little\n" +++ "awkward. Sorry! Here is the recommended process:\n" +++ "\n" +++ " 1. for each branch `foo', tag that branch with `foo-final';\n" +++ " 2. merge all branches together (--allow-conflicts may help);\n" +++ " 3. run `darcs optimize --reorder' on the result;\n" +++ " 4. run `darcs convert' to create a merged darcs-2 repository;\n" +++ " 5. re-create each branch by calling `darcs get --tag foo-final' on\n" +++ " the darcs-2 repository; and finally\n" +++ " 6. use `darcs obliterate' to delete the foo-final tags.\n"++-- | This part of the help is split out because it is used twice: in+-- the help string, and in the prompt for confirmation.+convert_help' :: String+convert_help' =+ "WARNING: the repository produced by this command is not understood by\n" +++ "Darcs 1.x, and patches cannot be exchanged between repositories in\n" +++ "darcs-1 and darcs-2 formats.\n" +++ "\n" +++ "Furthermore, darcs 2 repositories created by different invocations of\n" +++ "this command SHOULD NOT exchange patches, unless those repositories\n" +++ "had no patches in common when they were converted. (That is, within a\n" +++ "set of repos that exchange patches, no patch should be converted more\n" +++ "than once.)\n"++convert :: DarcsCommand+convert = DarcsCommand {command_name = "convert",+ command_help = convert_help,+ command_description = convert_description,+ command_extra_args = -1,+ command_extra_arg_help = ["<SOURCE>", "[<DESTINATION>]"],+ command_command = convert_cmd,+ command_prereq = \_ -> return $ Right (),+ command_get_arg_possibilities = return [],+ command_argdefaults = nodefaults,+ command_advanced_options = network_options,+ command_basic_options = [reponame,set_scripts_executable]}++convert_cmd :: [DarcsFlag] -> [String] -> IO ()+convert_cmd opts [inrepodir, outname] = convert_cmd (NewRepo outname:opts) [inrepodir]+convert_cmd orig_opts [inrepodir] = do+ putStrLn convert_help'+ let vow = "I understand the consequences of my action"+ putStrLn "Please confirm that you have read and understood the above"+ vow' <- askUser ("by typing `" ++ vow ++ "': ")+ when (vow' /= vow) $ fail "User didn't understand the consequences."+ let opts = UseFormat2:orig_opts+ typed_repodir <- ioAbsoluteOrRemote inrepodir+ let repodir = toPath typed_repodir+ show_motd opts repodir+ mysimplename <- make_repo_name opts repodir+ createDirectory mysimplename+ setCurrentDirectory mysimplename+ createRepository opts+ writeBinFile (darcsdir++"/hashed_inventory") ""+ withRepoLock (NoUpdateWorking:opts) $- \repositoryfoo ->+ withRepositoryDirectory opts repodir $- \themrepobar -> do+ -- We really ought to have special versions of withRepoLock and+ -- withRepositoryDirectory that check at runtime that it's the right+ -- sort of repository and accept a function of (Repository Patch) or+ -- (Repository (FL RealPatch)), but that seems like a lot of work+ -- when these functions would be used exactly once, right here. So I+ -- go with a horrible evil hack.++ -- The other alternative (which is what we used to do) is to use+ -- "universal" functions to do the conversion, but that's also+ -- unsatisfying.++ let repository = unsafeCoerce# repositoryfoo :: Repository (FL RealPatch)+ themrepo = unsafeCoerce# themrepobar :: Repository Patch+ theirstuff <- read_repo themrepo+ let patches = mapFL_FL convertNamed $ patchSetToPatches theirstuff+ inOrderTags = iot theirstuff+ where iot ((t:<:NilRL):<:r) = info t : iot r+ iot (NilRL:<:r) = iot r+ iot NilRL = []+ iot ((_:<:x):<:y) = iot (x:<:y)+ outOfOrderTags = catMaybes $ mapRL oot $ concatRL theirstuff+ where oot t = if is_tag (info t) && not (info t `elem` inOrderTags)+ then Just (info t, getdeps $ hopefully t)+ else Nothing+ fixDep p = case lookup p outOfOrderTags of+ Just d -> p : concatMap fixDep d+ Nothing -> [p]+ convertOne :: Patch -> FL RealPatch+ convertOne x | is_merger x = case mergeUnravelled $ public_unravel $ modernize_patch x of+ Just (FlippedSeal y) ->+ case effect y =/\= effect x of+ IsEq -> y :>: NilFL+ NotEq ->+ traceDoc (text "lossy conversion:" $$+ showPatch x)+ fromPrims (effect x)+ Nothing -> traceDoc (text+ "lossy conversion of complicated conflict:" $$+ showPatch x)+ fromPrims (effect x)+ | otherwise = case flattenFL x of+ NilFL -> NilFL+ (x':>:NilFL) -> fromPrims $ effect x'+ xs -> concatFL $ mapFL_FL convertOne xs+ convertNamed :: Named Patch -> PatchInfoAnd (FL RealPatch)+ convertNamed n = n2pia $+ adddeps (infopatch (convertInfo $ patch2patchinfo n) $+ convertOne $ patchcontents n)+ (map convertInfo $ concatMap fixDep $ getdeps n)+ convertInfo n | n `elem` inOrderTags = n+ | otherwise = maybe n (\t -> pi_rename n ("old tag: "++t)) $ pi_tag n+ applySome xs = do tentativelyMergePatches repository "convert" (AllowConflicts:opts) NilFL xs+ finalizeRepositoryChanges repository -- this is to clean out pristine.hashed+ revertRepositoryChanges repository+ sequence_ $ mapFL applySome $ bunchFL 100 $ progressFL "Converting patch" patches+ invalidateIndex repository+ revertable $ createPristineDirectoryTree repository "."+ when (SetScriptsExecutable `elem` opts) $+ do putVerbose $ text "Making scripts executable"+ c <- list_slurpy_files `fmap` slurp_recorded repository+ let setExecutableIfScript f =+ do contents <- B.readFile f+ when (BC.pack "#!" `B.isPrefixOf` contents) $ do+ putVerbose $ text ("Making executable: " ++ f)+ setExecutable f True+ mapM_ setExecutableIfScript c+ sync_repo repository+ optimizeInventory repository+ putInfo $ text "Finished converting."+ where am_verbose = Verbose `elem` orig_opts+ am_informative = not $ Quiet `elem` orig_opts+ putVerbose s = when am_verbose $ putDocLn s+ putInfo s = when am_informative $ putDocLn s+ revertable x = x `clarify_errors` unlines+ ["An error may have left your new working directory an inconsistent",+ "but recoverable state. You should be able to make the new",+ "repository consistent again by running darcs revert -a."]++convert_cmd _ _ = fail "You must provide 'convert' with either one or two arguments."++make_repo_name :: [DarcsFlag] -> FilePath -> IO String+make_repo_name (NewRepo n:_) _ =+ do exists <- doesDirectoryExist n+ file_exists <- doesFileExist n+ if exists || file_exists+ then fail $ "Directory or file named '" ++ n ++ "' already exists."+ else return n+make_repo_name (_:as) d = make_repo_name as d+make_repo_name [] d =+ case dropWhile (=='.') $ reverse $+ takeWhile (\c -> c /= '/' && c /= ':') $+ dropWhile (=='/') $ reverse d of+ "" -> modify_repo_name "anonymous_repo"+ base -> modify_repo_name base++modify_repo_name :: String -> IO String+modify_repo_name name =+ if head name == '/'+ then mrn name (-1)+ else do cwd <- getCurrentDirectory+ mrn (cwd ++ "/" ++ name) (-1)+ where+ mrn :: String -> Int -> IO String+ mrn n i = do+ exists <- doesDirectoryExist thename+ file_exists <- doesFileExist thename+ if not exists && not file_exists+ then do when (i /= -1) $+ putStrLn $ "Directory '"++ n +++ "' already exists, creating repository as '"+++ thename ++"'"+ return thename+ else mrn n $ i+1+ where thename = if i == -1 then n else n++"_"++show i+ +\end{code}
@@ -0,0 +1,245 @@+% Copyright (C) 2003-2004 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\darcsCommand{diff}+\begin{code}+{-# OPTIONS_GHC -cpp #-}+{-# LANGUAGE CPP #-}++module Darcs.Commands.Diff ( diff_command ) where++import System.FilePath.Posix ( takeFileName )+import System.Directory ( setCurrentDirectory )+import Workaround ( getCurrentDirectory )+import Darcs.Utils ( askUser, withCurrentDirectory )+import Control.Monad ( when )+import Data.List ( (\\) )++import Darcs.External( diff_program )+import CommandLine ( parseCmd )+import Darcs.Commands ( DarcsCommand(..), nodefaults )+import Darcs.Arguments ( DarcsFlag(DiffFlags, Unified, DiffCmd,+ LastN, AfterPatch),+ match_range, store_in_memory, + diff_cmd_flag, diffflags, unidiff,+ working_repo_dir, fixSubPaths,+ )+import Darcs.Hopefully ( info )+import Darcs.RepoPath ( toFilePath, sp2fn )+import Darcs.Match ( get_partial_first_match, get_partial_second_match,+ first_match, second_match,+ match_first_patchset, match_second_patchset )+import Darcs.Repository ( PatchSet, withRepository, ($-), read_repo,+ amInRepository, slurp_recorded_and_unrecorded,+ createPristineDirectoryTree,+ createPartialsPristineDirectoryTree )+import Darcs.SlurpDirectory ( get_path_list, writeSlurpy )+import Darcs.Patch ( RepoPatch )+import Darcs.Ordered ( mapRL, concatRL )+import Darcs.Patch.Info ( PatchInfo, human_friendly )+import Darcs.External ( execPipeIgnoreError, clonePaths )+import Darcs.Lock ( withTempDir )+import Darcs.Sealed ( unsafeUnseal )+import Printer ( Doc, putDoc, vcat, empty, ($$) )+#include "impossible.h"++diff_description :: String+diff_description = "Create a diff between two versions of the repository."++diff_help :: String+diff_help =+ "Diff can be used to create a diff between two versions which are in your\n"+++ "repository. Specifying just --from-patch will get you a diff against\n"+++ "your working copy. If you give diff no version arguments, it gives\n"+++ "you the same information as whatsnew except that the patch is\n"+++ "formatted as the output of a diff command\n"++diff_command :: DarcsCommand+diff_command = DarcsCommand {command_name = "diff",+ command_help = diff_help,+ command_description = diff_description,+ command_extra_args = -1,+ command_extra_arg_help+ = ["[FILE or DIRECTORY]..."],+ command_command = diff_cmd,+ command_prereq = amInRepository,+ command_get_arg_possibilities = return [],+ command_argdefaults = nodefaults,+ command_advanced_options = [],+ command_basic_options = [match_range,+ diff_cmd_flag,+ diffflags, unidiff,+ working_repo_dir, store_in_memory]}+\end{code}++\begin{options}+--diff-opts+\end{options}++Diff calls an external ``diff'' command to do the actual work, and passes+any unrecognized flags to this diff command. Thus you can call+\begin{verbatim}+% darcs diff -t 0.9.8 -t 0.9.10 -- -u+\end{verbatim}+to get a diff in the unified format. Actually, thanks to the wonders of+getopt you need the ``\verb!--!'' shown above before any arguments to diff.+You can also specify additional arguments to diff using the+\verb!--diff-opts! flag. The above command would look like this:+\begin{verbatim}+% darcs diff --diff-opts -u -t 0.9.8 -t 0.9.10+\end{verbatim}+This may not seem like an improvement, but it really pays off when you want+to always give diff the same options. You can do this by adding+\begin{verbatim}+% diff diff-opts -udp+\end{verbatim}+to your \verb!_darcs/prefs/defaults! file.++\begin{code}+get_diff_opts :: [DarcsFlag] -> [String]+get_diff_opts [] = []+get_diff_opts (Unified:fs) = "-u" : get_diff_opts fs+get_diff_opts (DiffFlags f:fs) = f : get_diff_opts fs+get_diff_opts (_:fs) = get_diff_opts fs++has_diff_cmd_flag :: [DarcsFlag] -> Bool+has_diff_cmd_flag (DiffCmd _:_) = True+has_diff_cmd_flag (_:t) = has_diff_cmd_flag t+has_diff_cmd_flag [] = False++-- | Returns the command we should use for diff as a tuple (command, arguments).+-- This will either be whatever the user specified via --diff-command or the+-- default 'diff_program'. Note that this potentially involves parsing the+-- user's diff-command, hence the possibility for failure with an exception.+get_diff_cmd_and_args :: String -> [DarcsFlag] -> String -> String+ -> Either String (String, [String])+get_diff_cmd_and_args cmd opts f1 f2 = helper opts where+ helper (DiffCmd c:_) =+ case parseCmd [ ('1', f1) , ('2', f2) ] c of+ Left err -> Left $ show err+ Right ([],_) -> bug $ "parseCmd should never return empty list"+ Right ((h:t),_) -> Right (h,t)+ helper [] = -- if no command specified, use 'diff'+ Right (cmd, ("-rN":get_diff_opts opts++[f1,f2]))+ helper (_:t) = helper t+\end{code}++If you want to view only the differences to one or more files, you can do+so with a command such as+\begin{verbatim}+% darcs diff foo.c bar.c baz/+\end{verbatim}++\begin{options}+--diff-command+\end{options}++You can use a different program to view differences by including+the flag \verb!--diff-command!, e.g.+\begin{verbatim}+--diff-command 'opendiff %1 %2'.+\end{verbatim}+The \verb!%1! and \verb!%2! are replaced with the two versions to be+merged. The above example works with the FileMerge.app tool that comes with+Apple's developer tools. To use xxdiff, you would use+\begin{verbatim}+--diff-command 'xxdiff %1 %2'+\end{verbatim}+To use \verb!kdiff3!, you can use+\begin{verbatim}+--diff-command 'kdiff3 %1 %2'+\end{verbatim}++Note that the command is split into space-separated words and the first one is+\verb!exec!ed with the rest as arguments---it is not a shell command. Also+the substitution of the \verb!%! escapes is only done on complete words.+See \ref{resolution} for how you might work around this fact, for example,+with Emacs' Ediff package.++Note also that the \verb!--diff-opts! flag is ignored if you use this option.++\begin{code}+diff_cmd :: [DarcsFlag] -> [String] -> IO ()+diff_cmd opts args = withRepository opts $- \repository -> do+ when (not (null [i | LastN i <- opts])+ && not (null [p | AfterPatch p <- opts])+ ) $+ fail ("using --patch and --last at the same time with the 'diff' command"+ ++ " doesn't make sense. Use --from-patch to create a diff from this"+ ++ " patch to the present, or use just '--patch' to view this specific"+ ++ " patch.")+ formerdir <- getCurrentDirectory+ path_list <- if null args+ then return []+ else map sp2fn `fmap` fixSubPaths opts args+ thename <- return $ takeFileName formerdir+ withTempDir ("old-"++thename) $ \odir -> do+ setCurrentDirectory formerdir+ withTempDir ("new-"++thename) $ \ndir -> do+ if first_match opts+ then withCurrentDirectory odir $+ get_partial_first_match repository opts path_list+ else if null path_list+ then createPristineDirectoryTree repository (toFilePath odir)+ else createPartialsPristineDirectoryTree repository path_list (toFilePath odir)+ if second_match opts+ then withCurrentDirectory ndir $+ get_partial_second_match repository opts path_list+ else do (_, s) <- slurp_recorded_and_unrecorded repository+ let ps = concatMap (get_path_list s . toFilePath) path_list+ if null path_list+ then withCurrentDirectory ndir $ writeSlurpy s "."+ else clonePaths formerdir (toFilePath ndir) ps+ thediff <- withCurrentDirectory (toFilePath odir ++ "/..") $+ case path_list of+ [] -> rundiff (takeFileName $ toFilePath odir) (takeFileName $ toFilePath ndir)+ fs -> vcat `fmap`+ mapM (\f -> rundiff+ (takeFileName (toFilePath odir) ++ "/" ++ toFilePath f)+ (takeFileName (toFilePath ndir) ++ "/" ++ toFilePath f)) fs+ morepatches <- read_repo repository+ putDoc $ changelog (get_diff_info opts morepatches)+ $$ thediff+ where rundiff :: String -> String -> IO Doc+ rundiff f1 f2 = do+ cmd <- diff_program+ case get_diff_cmd_and_args cmd opts f1 f2 of+ Left err -> fail err+ Right (d_cmd, d_args) ->+ let other_diff = has_diff_cmd_flag opts in+ do when other_diff $ putStrLn $+ "Running command '" ++ unwords (d_cmd:d_args) ++ "'"+ output <- execPipeIgnoreError d_cmd d_args empty+ when other_diff $ do+ askUser "Hit return to move on..."+ return ()+ return output++get_diff_info :: RepoPatch p => [DarcsFlag] -> PatchSet p -> [PatchInfo]+get_diff_info opts ps =+ let pi1s = mapRL info $ concatRL $ if first_match opts+ then unsafeUnseal $ match_first_patchset opts ps+ else ps+ pi2s = mapRL info $ concatRL $ if second_match opts+ then unsafeUnseal $ match_second_patchset opts ps+ else ps+ in pi2s \\ pi1s++changelog :: [PatchInfo] -> Doc+changelog pis = vcat $ map human_friendly pis+\end{code}+
@@ -0,0 +1,156 @@+% Copyright (C) 2003 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\darcsCommand{dist}+\begin{code}+module Darcs.Commands.Dist ( dist ) where+import System.Directory ( setCurrentDirectory )+import Workaround ( getCurrentDirectory )+import System.Exit ( ExitCode(..), exitWith )+import System.Cmd ( system )+import System.FilePath.Posix ( takeFileName, (</>) )+import Data.Char ( isAlphaNum )+import Control.Monad ( when )++import Darcs.Commands ( DarcsCommand(DarcsCommand, command_name, command_help,+ command_description, command_extra_args,+ command_extra_arg_help, command_command,+ command_prereq, command_get_arg_possibilities,+ command_argdefaults,+ command_advanced_options, command_basic_options),+ nodefaults )+import Darcs.Arguments ( DarcsFlag(Verbose, DistName), distname_option,+ working_repo_dir, match_one, store_in_memory,+ fixSubPaths )+import Darcs.Match ( get_nonrange_match, have_nonrange_match )+import Darcs.Repository ( amInRepository, withRepoReadLock, ($-), --withRecorded,+ createPartialsPristineDirectoryTree )+import Darcs.Repository.Prefs ( get_prefval )+import Darcs.Lock ( withTemp, withTempDir, readBinFile )+import Darcs.RepoPath ( AbsolutePath, toFilePath )+import Darcs.Utils ( withCurrentDirectory )+import Exec ( exec, Redirect(..) )++dist_description :: String+dist_description = "Create a distribution tarball."++dist_help :: String+dist_help =+ "The `darcs dist' command creates a compressed archive (a `tarball') in\n" +++ "the repository's root directory, containing the recorded state of the\n" +++ "working tree (unrecorded changes and the _darcs directory are\n" +++ "excluded).\n" +++ "\n" +++ "If a predist command is set (see `darcs setpref'), that command will\n" +++ "be run on the tarball contents prior to archiving. For example,\n" +++ "autotools projects would set it to `autoconf && automake'.\n" +++ "\n" +++ "By default, the tarball (and the top-level directory within the\n" +++ "tarball) has the same name as the repository, but this can be\n" +++ "overridden with the --dist-name option.\n"++ -- FIXME: this is tedious and ugly.+ {-+ ++ "\n" +++ "Suppose you use a version numbering scheme `major.minor.patch', and\n" +++ "you tag each release `major.minor'. You can then calculate the\n" +++ "version number by taking the newest tag and appending a dot and the\n" +++ "number of patches since that tag. If you use the directory name as\n" +++ "the project name, you can make tarballs of the form name-version.tgz\n" +++ "using the following shell script:\n" +++ "\n" +++ " major_minor=$(darcs show tags | head -1) &&\n" +++ " patch_level=$(($(darcs changes --count --from-tag .) - 1)) &&\n" +++ " version=$major_minor.$patch_level &&\n" +++ " project=${PWD##*/} &&\n" +++ " darcs dist --dist-name \"$project\"-\"$version\".tar.gz\n"+ -}++dist :: DarcsCommand+dist = DarcsCommand {command_name = "dist",+ command_help = dist_help,+ command_description = dist_description,+ command_extra_args = 0,+ command_extra_arg_help = [],+ command_command = dist_cmd,+ command_prereq = amInRepository,+ command_get_arg_possibilities = return [],+ command_argdefaults = nodefaults,+ command_advanced_options = [],+ command_basic_options = [distname_option,+ working_repo_dir,+ match_one,+ store_in_memory]}++dist_cmd :: [DarcsFlag] -> [String] -> IO ()+dist_cmd opts args = withRepoReadLock opts $- \repository -> do+ distname <- get_dist_name opts+ verb <- return $ Verbose `elem` opts+ predist <- get_prefval "predist"+ formerdir <- getCurrentDirectory+ path_list <- if null args+ then return [""]+ else map toFilePath `fmap` fixSubPaths opts args+ resultfile <- return (formerdir</>distname++".tar.gz")+ withTemp $ \tarfile ->+ withTempDir "darcsdist" $ \tempdir -> do+ setCurrentDirectory (formerdir)+ withTempDir (toFilePath tempdir </> takeFileName distname) $ \ddir -> do+ if have_nonrange_match opts+ then withCurrentDirectory ddir $ get_nonrange_match repository opts+ else createPartialsPristineDirectoryTree repository path_list (toFilePath ddir)+ ec <- case predist of Nothing -> return ExitSuccess+ Just pd -> system pd+ if (ec == ExitSuccess) then do_dist verb tarfile tempdir ddir resultfile+ else+ do+ putStrLn "Dist aborted due to predist failure"+ exitWith ec++-- | This function performs the actual distribution action itself.+-- NB - it does /not/ perform the pre-dist, that should already+-- have completed successfully before this is invoked.+do_dist :: Bool -> FilePath -> AbsolutePath -> AbsolutePath -> FilePath -> IO ()+do_dist verb tarfile tempdir ddir resultfile = do+ setCurrentDirectory (toFilePath tempdir)+ exec "tar" ["-cf", "-", safename $ takeFileName $ toFilePath ddir]+ (Null, File tarfile, AsIs)+ when verb $ withTemp $ \tar_listing -> do+ exec "tar" ["-tf", "-"]+ (File tarfile, File tar_listing, Stdout)+ to <- readBinFile tar_listing+ putStr to+ exec "gzip" ["-c"]+ (File tarfile, File resultfile, AsIs)+ putStrLn $ "Created dist as "++resultfile+ where+ safename n@(c:_) | isAlphaNum c = n+ safename n = "./" ++ n++guess_repo_name :: IO String+guess_repo_name = do+ pwd <- getCurrentDirectory+ if '/' `elem` pwd+ then return $ reverse $ takeWhile (/='/') $ reverse pwd+ else return "cantguessreponame"++get_dist_name :: [DarcsFlag] -> IO String+get_dist_name (DistName dn:_) = return dn+get_dist_name (_:fs) = get_dist_name fs+get_dist_name _ = guess_repo_name+\end{code}+
@@ -0,0 +1,180 @@+% Copyright (C) 2009 Ganesh Sittampalam+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\subsection{darcs gzcrcs}+\darcsCommand{gzcrcs}+\begin{code}+module Darcs.Commands.GZCRCs ( gzcrcs, doCRCWarnings ) where++import Control.Arrow ( (***) )+import Control.Monad ( when )+import Control.Monad.Trans ( liftIO )+import Control.Monad.Writer ( runWriter, runWriterT, tell )+import Data.List ( intersperse )+import Data.Monoid ( Any(..), Sum(..) )++import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as BL++import System.Directory ( getDirectoryContents, doesFileExist, doesDirectoryExist )+import System.Exit ( ExitCode(..), exitWith )++import Darcs.Commands ( DarcsCommand(..), nodefaults )+import Darcs.Arguments ( DarcsFlag( Quiet, Verbose, Check, Repair ),+ check_or_repair, working_repo_dir,+ )+import Darcs.Repository ( Repository, amInRepository, withRepository )+import Darcs.Patch ( RepoPatch )+import Printer ( putDocLn, text )+import ByteStringUtils ( isGZFile )+import Darcs.Lock ( gzWriteAtomicFilePSs )++-- This command needs access beyond the normal repository APIs (to+-- get at the caches and inspect them directly)+-- Could move the relevant code into Darcs.Repository modules+-- but it doesn't really seem worth it.+import Darcs.Repository.InternalTypes ( extractCache )+import Darcs.Repository.Cache ( Cache(..), writable, hashedFilePath, allHashedDirs )+++#ifdef HAVE_HASKELL_ZLIB+import Darcs.Global ( getCRCWarnings, resetCRCWarnings )+import ByteStringUtils ( gzDecompress )+#else+-- These functions aren't available unless we have the Haskell zlib.+-- The gzcrcs command shouldn't be enabled in this case, but we would+-- still like to typecheck this module as much as possible so we include+-- dummy versions +noChecking :: String -> a+noChecking what = error $ "Darcs.Commands.GZCRCs." ++ what ++ ": gz CRC checking is not possible unless " +++ "darcs has been built with the Haskell zlib. This code should be unreachable."+getCRCWarnings :: IO [FilePath]+getCRCWarnings = noChecking "getCRCWarnings"+resetCRCWarnings :: IO ()+resetCRCWarnings = noChecking "resetCRCWarnings"+gzDecompress :: forall m . (Functor m, Monad m) => m () -> Maybe Int -> BL.ByteString -> m [B.ByteString]+gzDecompress = noChecking "gzDecompress"+#endif++gzcrcs_description :: String+gzcrcs_description = "Check or repair the CRCs of compressed files in the repository."++gzcrcs_help :: String+gzcrcs_help = formatText+ [+ "Versions of darcs >=1.0.4 and <2.2.0 had a bug that caused compressed files " +++ "with bad CRCs (but valid data) to be written out. CRCs were not checked on " +++ "reading, so this bug wasn't noticed.",+ "This command inspects your repository and caches for this corruption and optionally " +++ "repairs it.",+ "You may wish to also run 'darcs check --complete' before repairing the corruption. " +++ "This is not done automatically because it might result in needing to fetch extra " +++ "patches if the repository is lazy.",+ "If there are any other problems with your repository, you can still repair the CRCs, " +++ "but you are advised to first make a backup copy in case the CRC errors are actually " +++ "caused by bad data and the old CRCs might be useful in recovering that data.",+ "If you were warned about CRC errors during an operation involving another repository, " +++ "then it is possible that the other repository contains the corrupt CRCs, so you " +++ "should arrange for that repository to also be checked/repaired.",+ "Note that readonly caches, or other repositories that were sources for lazy gets, " +++ "will be checked but not repaired. Also, this command will abort if it encounters " +++ "any non-CRC corruption in compressed files."+ ]++formatText :: [String] -> String+formatText = unlines . concat . intersperse [""] . map (map unwords . para 80 . words)++-- |Take a list of words and split it up so that each chunk fits into the specified width+-- when spaces are included. Any words longer than the specified width end up in a chunk+-- of their own.+para :: Int -> [[a]] -> [[[a]]]+para w = para'+ where para' [] = []+ para' xs = uncurry (:) $ para'' w xs+ para'' r (x:xs) | w == r || length x < r = ((x:) *** id) $ para'' (r - length x - 1) xs+ para'' _ xs = ([], para' xs)++-- |This is designed for use in an atexit handler, e.g. in Darcs.RunCommand+doCRCWarnings :: Bool -> IO ()+doCRCWarnings verbose = do+ files <- getCRCWarnings+ resetCRCWarnings+ when (not . null $ files) $ do+ putStr . formatText $+ ["",+ "Warning: CRC errors found. These are probably harmless but should " +++ "be repaired. See 'darcs gzcrcs --help' for more information.",+ ""]+ when verbose $ putStrLn $ unlines ("The following corrupt files were found:":files)++gzcrcs :: DarcsCommand+gzcrcs = DarcsCommand {command_name = "gzcrcs",+ command_help = gzcrcs_help,+ command_description = gzcrcs_description,+ command_extra_args = 0,+ command_extra_arg_help = [],+ command_command = gzcrcs_cmd,+ command_prereq = amInRepository,+ command_get_arg_possibilities = return [],+ command_argdefaults = nodefaults,+ command_advanced_options = [],+ command_basic_options = [check_or_repair,+ working_repo_dir+ ]}++gzcrcs_cmd :: [DarcsFlag] -> [String] -> IO ()+gzcrcs_cmd opts _ | Check `elem` opts || Repair `elem` opts = withRepository opts (gzcrcs' opts)+gzcrcs_cmd _ _ = error "You must specify --check or --repair for gzcrcs"++gzcrcs' :: (RepoPatch p) => [DarcsFlag] -> Repository p -> IO ()+gzcrcs' opts repo = do+ let Ca locs = extractCache repo+ ((), Any checkFailed) <- runWriterT $ flip mapM_ locs $ \loc -> do+ let w = writable loc+ flip mapM_ allHashedDirs $ \hdir -> do+ let dir = hashedFilePath loc hdir ""+ exists <- liftIO $ doesDirectoryExist dir+ when exists $ do+ liftIO $ putInfo $ text $ "Checking " ++ dir ++ (if w then "" else " (readonly)")+ files <- liftIO $ getDirectoryContents dir+ ((), Sum (count :: Int)) <- runWriterT $ flip mapM_ files $ \file -> do+ let fn = dir ++ file+ isfile <- liftIO $ doesFileExist fn+ when isfile $ do+ gz <- liftIO $ isGZFile fn+ case gz of+ Nothing -> return ()+ Just len -> do+ contents <- liftIO $ B.readFile fn+ let (uncompressed, Any isCorrupt)+ = runWriter . gzDecompress (tell (Any True)) (Just len) . BL.fromChunks $ [contents]+ when isCorrupt $ do+ tell (Sum 1) -- count of files in current directory+ liftIO $ putVerbose $ text $ "Corrupt: " ++ fn+ when (w && Repair `elem` opts) $ liftIO $ gzWriteAtomicFilePSs fn uncompressed+ when (count > 0) $ do+ liftIO $ putInfo $ text $+ "Found " ++ show count ++ " corrupt file" ++ (if count > 1 then "s" else "") +++ (if Repair `elem` opts then (if w then " (repaired)" else " (not repaired") else "")+ tell (Any True) -- something corrupt somewhere+ when (Check `elem` opts && checkFailed) $ exitWith $ ExitFailure 1++ where+ putInfo s = when (not $ Quiet `elem` opts) $ putDocLn s+ putVerbose s = when (Verbose `elem` opts) $ putDocLn s++\end{code}
@@ -0,0 +1,347 @@+% Copyright (C) 2002-2005 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\darcsCommand{get}+\begin{code}+{-# OPTIONS_GHC -cpp #-}+{-# LANGUAGE CPP #-}++module Darcs.Commands.Get ( get, clone ) where++import System.Directory ( setCurrentDirectory, doesDirectoryExist, doesFileExist,+ createDirectory )+import Workaround ( getCurrentDirectory )+import Data.Maybe ( isJust )+import Control.Monad ( when )++import Darcs.Commands ( DarcsCommand(..), nodefaults, command_alias )+import Darcs.Arguments ( DarcsFlag( NewRepo, Partial, Lazy,+ UseFormat2, UseOldFashionedInventory, UseHashedInventory,+ SetScriptsExecutable, Quiet, OnePattern ),+ get_context, get_inventory_choices,+ partial, reponame,+ match_one_context, set_default, set_scripts_executable, nolinks,+ network_options )+import Darcs.Repository ( Repository, withRepository, ($-), withRepoLock, identifyRepositoryFor, read_repo,+ createPristineDirectoryTree,+ tentativelyRemovePatches, patchSetToPatches, patchSetToRepository,+ copyRepository, tentativelyAddToPending,+ finalizeRepositoryChanges, sync_repo, setScriptsExecutable )+import Darcs.Repository.Format ( identifyRepoFormat, RepoFormat,+ RepoProperty ( Darcs2, HashedInventory ), format_has )+import Darcs.Repository.DarcsRepo ( write_inventory )+import qualified Darcs.Repository.DarcsRepo as DR ( read_repo )+import Darcs.Repository ( PatchSet, SealedPatchSet, copy_oldrepo_patches,+ createRepository)+import Darcs.Repository.ApplyPatches ( apply_patches )+import Darcs.Repository.Checkpoint ( write_checkpoint_patch, get_checkpoint )+import Darcs.Patch ( RepoPatch, Patch, apply, patch2patchinfo, invert,+ effect, description )+import Darcs.Ordered ( (:\/:)(..), RL(..), unsafeUnRL, mapRL, concatRL, reverseRL, lengthFL )+import Darcs.External ( copyFileOrUrl, Cachable(..) )+import Darcs.Patch.Depends ( get_common_and_uncommon, get_patches_beyond_tag )+import Darcs.Repository.Prefs ( set_defaultrepo )+import Darcs.Repository.Motd ( show_motd )+import Darcs.Repository.Pristine ( identifyPristine, createPristineFromWorking, )+import Darcs.SignalHandler ( catchInterrupt )+import Darcs.Commands.Init ( initialize )+import Darcs.Match ( have_patchset_match, get_one_patchset )+import Darcs.Utils ( catchall, formatPath, withCurrentDirectory, prettyError )+import Progress ( debugMessage )+import Printer ( text, vcat, errorDoc, ($$), Doc, putDocLn, )+import Darcs.Lock ( writeBinFile )+import Darcs.RepoPath ( toFilePath, toPath, ioAbsoluteOrRemote)+import Darcs.Sealed ( Sealed(..), unsafeUnflippedseal )+import Darcs.Global ( darcsdir )+import English ( englishNum, Noun(..) )+import Darcs.Gorsvet( invalidateIndex )+#include "impossible.h"++get_description :: String+get_description = "Create a local copy of a repository."++get_help :: String+get_help =+ "Get creates a local copy of a repository. The optional second\n" +++ "argument specifies a destination directory for the new copy; if\n" +++ "omitted, it is inferred from the source location.\n" +++ "\n" +++ "By default Darcs will copy every patch from the original repository.\n" +++ "This means the copy is completely independent of the original; you can\n" +++ "operate on the new repository even when the original is inaccessible.\n" +++ "If you expect the original repository to remain accessible, you can\n" +++ "use --lazy to avoid copying patches until they are needed (`copy on\n" +++ "demand'). This is particularly useful when copying a remote\n" +++ "repository with a long history that you don't care about.\n" +++ "\n" +++ "The --lazy option isn't as useful for local copies, because Darcs will\n" +++ "automatically use `hard linking' where possible. As well as saving\n" +++ "time and space, you can move or delete the original repository without\n" +++ "affecting a complete, hard-linked copy. Hard linking requires that\n" +++ "the copy be on the same filesystem and the original repository, and\n" +++ "that the filesystem support hard linking. This includes NTFS, HFS+\n" +++ "and all general-purpose Unix filesystems (such as ext3, UFS and ZFS).\n" +++ "FAT does not support hard links.\n" +++ "\n" +++ "Darcs get will not copy unrecorded changes to the source repository's\n" +++ "working tree.\n" +++ "\n" +++ get_help_tag +++ "\n" +++ -- The remaining help text covers backwards-compatibility options.+ get_help_partial +++ "\n" +++ "A repository created by `darcs get' will be in the best available\n" +++ "format: it will be able to exchange patches with the source\n" +++ "repository, but will not be directly readable by Darcs binaries older\n" +++ "than 2.0.0. Use the `--old-fashioned-inventory' option if the latter\n" +++ "is required.\n"++get :: DarcsCommand+get = DarcsCommand {command_name = "get",+ command_help = get_help,+ command_description = get_description,+ command_extra_args = -1,+ command_extra_arg_help = ["<REPOSITORY>", "[<DIRECTORY>]"],+ command_command = get_cmd,+ command_prereq = contextExists,+ command_get_arg_possibilities = return [],+ command_argdefaults = nodefaults,+ command_advanced_options = network_options +++ command_advanced_options initialize,+ command_basic_options = [reponame,+ partial,+ match_one_context,+ set_default,+ set_scripts_executable,+ nolinks,+ get_inventory_choices]}++clone :: DarcsCommand+clone = command_alias "clone" get++get_cmd :: [DarcsFlag] -> [String] -> IO ()+get_cmd opts [inrepodir, outname] = get_cmd (NewRepo outname:opts) [inrepodir]+get_cmd opts [inrepodir] = do+ debugMessage "Starting work on get..."+ typed_repodir <- ioAbsoluteOrRemote inrepodir+ let repodir = toPath typed_repodir+ show_motd opts repodir+ when (Partial `elem` opts) $ debugMessage "Reading checkpoint..."+ rfsource_or_e <- identifyRepoFormat repodir+ rfsource <- case rfsource_or_e of Left e -> fail e+ Right x -> return x+ debugMessage $ "Found the format of "++repodir++"..."+ mysimplename <- make_repo_name opts repodir+ createDirectory mysimplename+ setCurrentDirectory mysimplename+ when (format_has Darcs2 rfsource && UseOldFashionedInventory `elem` opts) $+ putInfo $ text "Warning: 'old-fashioned-inventory' is ignored with a darcs-2 repository\n"+ let opts' = if format_has Darcs2 rfsource+ then UseFormat2:opts+ else if not (UseOldFashionedInventory `elem` opts)+ then UseHashedInventory:filter (/= UseFormat2) opts+ else UseOldFashionedInventory:filter (/= UseFormat2) opts+ createRepository opts'+ debugMessage "Finished initializing new directory."+ set_defaultrepo repodir opts++ rf_or_e <- identifyRepoFormat "."+ rf <- case rf_or_e of Left e -> fail e+ Right x -> return x+ if format_has HashedInventory rf -- refactor this into repository+ then writeBinFile (darcsdir++"/hashed_inventory") ""+ else write_inventory "." (NilRL:<:NilRL :: PatchSet Patch)++ if not (null [p | OnePattern p <- opts]) -- --to-match given+ && not (Partial `elem` opts) && not (Lazy `elem` opts)+ then withRepository opts $- \repository -> do+ debugMessage "Using economical get --to-match handling"+ fromrepo <- identifyRepositoryFor repository repodir+ Sealed patches_to_get <- get_one_patchset fromrepo opts+ patchSetToRepository fromrepo patches_to_get opts+ debugMessage "Finished converting selected patch set to new repository"+ else copy_repo_and_go_to_chosen_version opts repodir rfsource rf putInfo+ where am_informative = not $ Quiet `elem` opts+ putInfo s = when am_informative $ putDocLn s++get_cmd _ _ = fail "You must provide 'get' with either one or two arguments."++-- | called by get_cmd+-- assumes that the target repo of the get is the current directory, and that an inventory in the+-- right format has already been created.+copy_repo_and_go_to_chosen_version :: [DarcsFlag] -> String -> RepoFormat -> RepoFormat -> (Doc -> IO ()) -> IO ()+copy_repo_and_go_to_chosen_version opts repodir rfsource rf putInfo = do+ copy_repo `catchInterrupt` (putInfo $ text "Using lazy repository.")+ withRepository opts $- \repository -> go_to_chosen_version repository putInfo opts+ putInfo $ text "Finished getting."+ where copy_repo =+ withRepository opts $- \repository -> do+ if format_has HashedInventory rf || format_has HashedInventory rfsource+ then do debugMessage "Identifying and copying repository..."+ identifyRepositoryFor repository repodir >>= copyRepository+ when (SetScriptsExecutable `elem` opts) setScriptsExecutable+ else copy_repo_old_fashioned repository opts repodir++make_repo_name :: [DarcsFlag] -> FilePath -> IO String+make_repo_name (NewRepo n:_) _ =+ do exists <- doesDirectoryExist n+ file_exists <- doesFileExist n+ if exists || file_exists+ then fail $ "Directory or file named '" ++ n ++ "' already exists."+ else return n+make_repo_name (_:as) d = make_repo_name as d+make_repo_name [] d =+ case dropWhile (=='.') $ reverse $+ takeWhile (\c -> c /= '/' && c /= ':') $+ dropWhile (=='/') $ reverse d of+ "" -> modify_repo_name "anonymous_repo"+ base -> modify_repo_name base++modify_repo_name :: String -> IO String+modify_repo_name name =+ if head name == '/'+ then mrn name (-1)+ else do cwd <- getCurrentDirectory+ mrn (cwd ++ "/" ++ name) (-1)+ where+ mrn :: String -> Int -> IO String+ mrn n i = do+ exists <- doesDirectoryExist thename+ file_exists <- doesFileExist thename+ if not exists && not file_exists+ then do when (i /= -1) $+ putStrLn $ "Directory '"++ n +++ "' already exists, creating repository as '"+++ thename ++"'"+ return thename+ else mrn n $ i+1+ where thename = if i == -1 then n else n++"_"++show i++get_help_tag :: String+get_help_tag =+ "It is often desirable to make a copy of a repository that excludes\n" +++ "some patches. For example, if releases are tagged then `darcs get\n" +++ "--tag .' would make a copy of the repository as at the latest release.\n" +++ "\n" +++ "An untagged repository state can still be identified unambiguously by\n" +++ "a context file, as generated by `darcs changes --context'. Given the\n" +++ "name of such a file, the --context option will create a repository\n" +++ "that includes only the patches from that context. When a user reports\n" +++ "a bug in an unreleased version of your project, the recommended way to\n" +++ "find out exactly what version they were running is to have them\n" +++ "include a context file in the bug report.\n" +++ "\n" +++ "You can also make a copy of an untagged state using the --to-patch or\n" +++ "--to-match options, which exclude patches `after' the first matching\n" +++ "patch. Because these options treat the set of patches as an ordered\n" +++ "sequence, you may get different results after reordering with `darcs\n" +++ "optimize', so tagging is preferred.\n"++contextExists :: [DarcsFlag] -> IO (Either String ())+contextExists opts =+ case get_context opts of+ Nothing -> return $ Right ()+ Just f -> do exists <- doesFileExist $ toFilePath f+ if exists+ then return $ Right ()+ else return . Left $ "Context file "++toFilePath f++" does not exist"++go_to_chosen_version :: RepoPatch p => Repository p -> (Doc -> IO ())+ -> [DarcsFlag] -> IO ()+go_to_chosen_version repository putInfo opts =+ when (have_patchset_match opts) $ do+ debugMessage "Going to specified version..."+ patches <- read_repo repository+ Sealed context <- get_one_patchset repository opts+ let (_,us':\/:them') = get_common_and_uncommon (patches, context)+ case them' of+ NilRL:<:NilRL -> return ()+ _ -> errorDoc $ text "Missing these patches from context:"+ $$ (vcat $ mapRL description $ head $ unsafeUnRL them')+ let ps = patchSetToPatches us'+ putInfo $ text $ "Unapplying " ++ (show $ lengthFL ps) ++ " " +++ (englishNum (lengthFL ps) (Noun "patch") "")+ invalidateIndex repository+ withRepoLock opts $- \_ ->+ do tentativelyRemovePatches repository opts ps+ tentativelyAddToPending repository opts $ invert $ effect ps+ finalizeRepositoryChanges repository+ apply opts (invert $ effect ps) `catch` \e ->+ fail ("Couldn't undo patch in working dir.\n" ++ show e)+ sync_repo repository+++get_help_partial :: String+get_help_partial =+ "If the source repository is in a legacy darcs-1 format and contains at\n" +++ "least one checkpoint (see `darcs optimize'), the --partial option will\n" +++ "create a partial repository. A partial repository discards history\n" +++ "from before the checkpoint in order to reduce resource requirements.\n" +++ "For modern darcs-2 repositories, --partial is a deprecated alias for\n" +++ "the --lazy option.\n"++copy_repo_old_fashioned :: RepoPatch p => Repository p -> [DarcsFlag] -> String -> IO ()+copy_repo_old_fashioned repository opts repodir = do+ myname <- getCurrentDirectory+ fromrepo <- identifyRepositoryFor repository repodir+ mch <- get_checkpoint fromrepo+ patches <- read_repo fromrepo+ debugMessage "Getting the inventory..."+ write_inventory "." patches+ debugMessage "Copying patches..."+ copy_oldrepo_patches opts fromrepo "."+ debugMessage "Patches copied"+ Sealed local_patches <- DR.read_repo opts "." :: IO (SealedPatchSet Patch)+ debugMessage "Repo read"+ repo_is_local <- doesDirectoryExist repodir+ debugMessage $ "Repo local: " ++ formatPath (show repo_is_local)+ if repo_is_local && not (Partial `elem` opts)+ then do+ debugMessage "Copying prefs"+ copyFileOrUrl opts+ (repodir++"/"++darcsdir++"/prefs/prefs") (darcsdir++"/prefs/prefs") (MaxAge 600)+ `catchall` return ()+ debugMessage "Writing working directory"+ createPristineDirectoryTree fromrepo myname+ withCurrentDirectory myname $ do+ -- note: SetScriptsExecutable is normally checked in PatchApply+ -- but darcs get on local repositories does not apply patches+ if SetScriptsExecutable `elem` opts+ then setScriptsExecutable+ else return ()+ else do+ setCurrentDirectory myname+ if Partial `elem` opts && isJust mch+ then let Sealed p_ch = fromJust mch+ pi_ch = patch2patchinfo p_ch+ needed_patches = reverseRL $ concatRL $ unsafeUnflippedseal $+ get_patches_beyond_tag pi_ch local_patches+ in do write_checkpoint_patch p_ch+ apply opts p_ch `catch`+ \e -> fail ("Bad checkpoint!!!\n" ++ prettyError e)+ apply_patches opts needed_patches+ else apply_patches opts $ reverseRL $ concatRL local_patches+ debugMessage "Writing the pristine"+ pristine <- identifyPristine+ createPristineFromWorking pristine+ setCurrentDirectory myname+ debugMessage "Syncing the repository..."+ sync_repo repository+ debugMessage "Repository synced."++\end{code}
@@ -0,0 +1,266 @@+% Copyright (C) 2002-2004 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\darcsCommand{help}+\begin{code}+module Darcs.Commands.Help (+ help_cmd,+ command_control_list, environmentHelp, -- these are for preproc.hs+ print_version,+ list_available_commands ) where++import Darcs.Arguments ( DarcsFlag(..), environmentHelpSendmail )+import Darcs.Commands (+ CommandArgs(..), CommandControl(..), DarcsCommand(..),+ disambiguate_commands, extract_commands, get_command_help, nodefaults, usage )+import Darcs.External ( viewDoc )+import Darcs.Lock ( environmentHelpTmpdir, environmentHelpKeepTmpdir )+import Darcs.Patch.Match ( helpOnMatchers )+import Darcs.Repository.Prefs ( binaries_file_help, environmentHelpHome )+import Darcs.Utils ( withCurrentDirectory, environmentHelpEditor, environmentHelpPager )+import Data.Char ( isAlphaNum, toLower )+import Data.List ( groupBy )+import English ( andClauses )+import Printer ( text )+import Ssh ( environmentHelpSsh, environmentHelpScp, environmentHelpSshPort )+import System.Exit ( ExitCode(..), exitWith )+import ThisVersion ( darcs_version )+import URL (environmentHelpProxy, environmentHelpProxyPassword)+import Workaround ( getCurrentDirectory )+import qualified Darcs.TheCommands as TheCommands++help_description :: String+help_description = "Display help about darcs and darcs commands."++help_help :: String+help_help =+ "Without arguments, `darcs help' prints a categorized list of darcs\n" +++ "commands and a short description of each one. With an extra argument,\n" +++ "`darcs help foo' prints detailed help about the darcs command foo.\n"++help :: DarcsCommand+help = DarcsCommand {command_name = "help",+ command_help = help_help,+ command_description = help_description,+ command_extra_args = -1,+ command_extra_arg_help = ["[<DARCS_COMMAND> [DARCS_SUBCOMMAND]] "],+ command_command = \ x y -> help_cmd x y >> exitWith ExitSuccess,+ command_prereq = \_ -> return $ Right (),+ command_get_arg_possibilities = return [],+ command_argdefaults = nodefaults,+ command_advanced_options = [],+ command_basic_options = []}++help_cmd :: [DarcsFlag] -> [String] -> IO ()+help_cmd _ ["manpage"] = putStr $ unlines manpageLines+help_cmd _ ["patterns"] = viewDoc $ text $ helpOnMatchers+help_cmd _ ["environment"] = viewDoc $ text $ helpOnEnvironment+help_cmd _ [] = viewDoc $ text $ usage command_control_list++help_cmd _ (cmd:args) =+ let disambiguated = disambiguate_commands command_control_list cmd args+ in case disambiguated of+ Left err -> fail err+ Right (cmds,_) ->+ let msg = case cmds of+ CommandOnly c -> get_command_help Nothing c+ SuperCommandOnly c -> get_command_help Nothing c+ SuperCommandSub c s -> get_command_help (Just c) s+ in viewDoc $ text msg++list_available_commands :: IO ()+list_available_commands =+ do here <- getCurrentDirectory+ is_valid <- sequence $ map+ (\c-> withCurrentDirectory here $ (command_prereq c) [])+ (extract_commands command_control_list)+ putStr $ unlines $ map (command_name . fst) $+ filter (isRight.snd) $+ zip (extract_commands command_control_list) is_valid+ putStrLn "--help"+ putStrLn "--version"+ putStrLn "--exact-version"+ putStrLn "--overview"+ where isRight (Right _) = True+ isRight _ = False++print_version :: IO () +print_version = putStrLn $ "darcs version " ++ darcs_version++-- avoiding a module import cycle between Help and TheCommands+command_control_list :: [CommandControl] +command_control_list =+ Command_data help : TheCommands.command_control_list++-- | Help on each environment variable in which Darcs is interested.+environmentHelp :: [([String], [String])]+environmentHelp = [+ -- General-purpose+ environmentHelpHome,+ environmentHelpEditor,+ environmentHelpPager,+ environmentHelpTmpdir,+ environmentHelpKeepTmpdir,+ environmentHelpSendmail,+ -- Remote Repositories+ environmentHelpSsh,+ environmentHelpScp,+ environmentHelpSshPort,+ environmentHelpProxy,+ environmentHelpProxyPassword]++-- | The rendered form of the data in 'environment_help'.+helpOnEnvironment :: String+helpOnEnvironment =+ "Environment Variables\n" +++ "=====================\n\n" +++ unlines [andClauses ks ++ ":\n" +++ (unlines $ map (" " ++) ds)+ | (ks, ds) <- environmentHelp]++-- | This module is responsible for emitting a darcs "man-page", a+-- reference document used widely on Unix-like systems. Manpages are+-- primarily used as a quick reference, or "memory jogger", so the+-- output should be terser than the user manual.+--+-- Before modifying the output, please be sure to read the man(7) and+-- man-pages(7) manpages, as these respectively describe the relevant+-- syntax and conventions.++-- | The lines of the manpage to be printed.+manpageLines :: [String]+manpageLines = [+ ".TH DARCS 1 \"" ++ darcs_version ++ "\"",+ ".SH NAME",+ "darcs \\- an advanced revision control system",+ ".SH SYNOPSIS",+ ".B darcs", ".I command", ".RI < arguments |[ options ]>...",+ "",+ "Where the", ".I commands", "and their respective", ".I arguments", "are",+ "",+ unlines synopsis,+ ".SH DESCRIPTION",+ -- FIXME: this is copy-and-pasted from darcs.cabal, so+ -- it'll get out of date as people forget to maintain+ -- both in sync.+ "Darcs is a free, open source revision control",+ "system. It is:",+ ".TP 3", "\\(bu",+ "Distributed: Every user has access to the full",+ "command set, removing boundaries between server and",+ "client or committer and non-committers.",+ ".TP", "\\(bu",+ "Interactive: Darcs is easy to learn and efficient to",+ "use because it asks you questions in response to",+ "simple commands, giving you choices in your work",+ "flow. You can choose to record one change in a file,",+ "while ignoring another. As you update from upstream,",+ "you can review each patch name, even the full `diff'",+ "for interesting patches.",+ ".TP", "\\(bu",+ "Smart: Originally developed by physicist David",+ "Roundy, darcs is based on a unique algebra of",+ "patches.",+ "This smartness lets you respond to changing demands",+ "in ways that would otherwise not be possible. Learn",+ "more about spontaneous branches with darcs.",+ ".SH OPTIONS",+ "Different options are accepted by different Darcs commands.",+ "Each command's most important options are listed in the",+ ".B COMMANDS",+ "section. For a full list of all options accepted by",+ "a particular command, run `darcs", ".I command", "--help'.",+ ".SS " ++ helpOnMatchers, -- FIXME: this is a kludge.+ ".SH COMMANDS",+ unlines commands,+ unlines environment,+ ".SH FILES",+ ".SS \"_darcs/prefs/binaries\"",+ unlines binaries_file_help,+ ".SH BUGS",+ "At http://bugs.darcs.net/ you can find a list of known",+ "bugs in Darcs. Unknown bugs can be reported at that",+ "site (after creating an account) or by emailing the",+ "report to bugs@darcs.net.",+ -- ".SH EXAMPLE",+ -- FIXME:+ -- new project: init, rec -la;+ -- track upstream project: get, pull -a;+ -- contribute to project: add, rec, push/send.+ ".SH SEE ALSO",+ "A user manual is included with Darcs, in PDF and HTML",+ "form. It can also be found at http://darcs.net/manual/."]+ where+ -- | A synopsis line for each command. Uses 'foldl' because it is+ -- necessary to avoid blank lines from Hidden_commands, as groff+ -- translates them into annoying vertical padding (unlike TeX).+ synopsis :: [String]+ synopsis = foldl iter [] command_control_list+ where iter :: [String] -> CommandControl -> [String]+ iter acc (Group_name _) = acc+ iter acc (Hidden_command _) = acc+ iter acc (Command_data c@SuperCommand {}) =+ acc ++ concatMap+ (render (command_name c ++ " "))+ (extract_commands (command_sub_commands c))+ iter acc (Command_data c) = acc ++ render "" c+ render :: String -> DarcsCommand -> [String]+ render prefix c =+ [".B darcs " ++ prefix ++ command_name c] +++ (map mangle_args $ command_extra_arg_help c) +++ -- In the output, we want each command to be on its own+ -- line, but we don't want blank lines between them.+ -- AFAICT this can only be achieved with the .br+ -- directive, which is probably a GNUism.+ [".br"]++ -- | As 'synopsis', but make each group a subsection (.SS), and+ -- include the help text for each command.+ commands :: [String]+ commands = foldl iter [] command_control_list+ where iter :: [String] -> CommandControl -> [String]+ iter acc (Group_name x) = acc ++ [".SS \"" ++ x ++ "\""]+ iter acc (Hidden_command _) = acc+ iter acc (Command_data c@SuperCommand {}) =+ acc ++ concatMap+ (render (command_name c ++ " "))+ (extract_commands (command_sub_commands c))+ iter acc (Command_data c) = acc ++ render "" c+ render :: String -> DarcsCommand -> [String]+ render prefix c =+ [".B darcs " ++ prefix ++ command_name c] +++ (map mangle_args $ command_extra_arg_help c) +++ [".RS 4", command_help c, ".RE"]++ -- | Now I'm showing off: mangle the extra arguments of Darcs commands+ -- so as to use the ideal format for manpages, italic words and roman+ -- punctuation.+ mangle_args :: String -> String+ mangle_args s =+ ".RI " ++ (unwords $ map show (groupBy cmp $ map toLower $ gank s))+ where cmp x y = not $ xor (isAlphaNum x) (isAlphaNum y)+ xor x y = (x && not y) || (y && not x)+ gank (' ':'o':'r':' ':xs) = '|' : gank xs+ gank (x:xs) = x : gank xs+ gank [] = []++ environment :: [String]+ environment = ".SH ENVIRONMENT" : concat+ [(".SS \"" ++ andClauses ks ++ "\"") : ds+ | (ks, ds) <- environmentHelp]++\end{code}
@@ -0,0 +1,89 @@+% Copyright (C) 2002-2003 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\darcsCommand{initialize}+\begin{code}+module Darcs.Commands.Init ( initialize, initialize_cmd ) where+import Darcs.Commands ( DarcsCommand(..), nodefaults )+import Darcs.Arguments ( DarcsFlag, working_repo_dir,+ inventory_choices )+import Darcs.Repository ( amNotInRepository, createRepository )++initialize_description :: String+initialize_description = "Make the current directory a repository."++initialize_help :: String+initialize_help =+ "The `darcs initialize' command turns the current directory into a\n" +++ "Darcs repository. Any existing files and subdirectories become\n" +++ "UNSAVED changes in the working tree: record them with `darcs add -r'\n" +++ "and `darcs record'.\n" +++ "\n" +++ -- FIXME: confirm my assertion re conversion is true. --twb, 2008-12-14+ "When converting a project to Darcs from some other VCS, translating\n" +++ "the full revision history to native Darcs patches is recommended.\n" +++ "(The Darcs wiki lists utilities for this.) Because Darcs is optimized\n" +++ "for small patches, simply importing the latest revision as a single\n" +++ "large patch can PERMANENTLY degrade Darcs performance in your\n" +++ "repository by an order of magnitude.\n" +++ "\n" +++ "This command creates the `_darcs' directory, which stores version\n" +++ "control metadata. It also contains per-repository settings in\n" +++ "_darcs/prefs/, which you can read about in the user manual.\n" +++ "\n" +++ "In addition to the default `darcs-2' format, there are two backward\n" +++ "compatibility formats for the _darcs directory. These formats are\n" +++ "only useful if some of your contributors do not have access to Darcs\n" +++ "2.0 or higher. In that case, you need to use the original format\n" +++ "(called `old-fashioned inventory' or `darcs-1') for any repositories\n" +++ "those contributors access.\n" +++ "\n" +++ "As patches cannot be shared between darcs-2 and darcs-1 repositories,\n" +++ "you cannot use the darcs-2 format for private branches of such a\n" +++ "project. Instead, you should use the `hashed' format, which provides\n" +++ "most of the features of the darcs-2 format, while retaining the\n" +++ "ability to share patches with darcs-1 repositories. The `darcs get'\n" +++ "command will do this by default.\n" +++ "\n" +++ "Once all contributors have access to Darcs 2.0 or higher, a darcs-1\n" +++ "project can be migrated to darcs-2 using the `darcs convert' command.\n" +++ "\n" +++ "Darcs will create a hashed repository by default when you `darcs get'\n" +++ "a repository in old-fashioned inventory format. Once all contributors\n" +++ "have upgraded to Darcs 2.0 or later, use `darcs convert' to convert\n" +++ "the project to the darcs-2 format.\n" +++ "\n" +++ "Initialize is commonly abbreviated to `init'.\n"++initialize :: DarcsCommand+initialize = DarcsCommand {command_name = "initialize",+ command_help = initialize_help,+ command_description = initialize_description,+ command_extra_args = 0,+ command_extra_arg_help = [],+ command_prereq = amNotInRepository,+ command_command = initialize_cmd,+ command_get_arg_possibilities = return [],+ command_argdefaults = nodefaults,+ command_advanced_options = [],+ command_basic_options = [inventory_choices,+ working_repo_dir]}++initialize_cmd :: [DarcsFlag] -> [String] -> IO ()+initialize_cmd opts _ = createRepository opts+\end{code}+
@@ -0,0 +1,106 @@+% Copyright (C) 2002-2003,2005 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\darcsCommand{mark-conflicts}+\begin{code}+{-# OPTIONS_GHC -cpp #-}+{-# LANGUAGE CPP #-}++module Darcs.Commands.MarkConflicts ( markconflicts, resolve ) where+import System.Exit ( ExitCode(..), exitWith )+import Darcs.SignalHandler ( withSignalsBlocked )+import Control.Monad ( when )++import Darcs.Commands ( DarcsCommand(..), nodefaults, command_alias )+import Darcs.Arguments ( DarcsFlag, ignoretimes, working_repo_dir, umask_option )+import Darcs.Repository ( withRepoLock, ($-), amInRepository, add_to_pending,+ applyToWorking,+ read_repo, sync_repo, get_unrecorded_unsorted,+ )+import Darcs.Patch ( invert )+import Darcs.Ordered ( FL(..) )+import Darcs.Sealed ( Sealed(Sealed) )+import Darcs.Resolution ( patchset_conflict_resolutions )+import Darcs.Utils ( promptYorn )+#include "impossible.h"++markconflicts_description :: String+markconflicts_description =+ "Mark unresolved conflicts in working tree, for manual resolution."++markconflicts_help :: String+markconflicts_help =+ "Darcs requires human guidance to unify changes to the same part of a\n" +++ "source file. When a conflict first occurs, darcs will add both\n" +++ "choices to the working tree, delimited by the markers `v v v',\n" +++ "`* * *' and `^ ^ ^'.\n" +++ "\n" +++ "However, you might revert or manually delete these markers without\n" +++ "actually resolving the conflict. In this case, `darcs mark-conflicts'\n" +++ "is useful to show where any unresolved conflicts. It is also useful\n" +++ "if `darcs apply' is called with --apply-conflicts, where conflicts\n" +++ "aren't marked initially.\n" +++ "\n" +++ "Any unrecorded changes to the working tree WILL be lost forever when\n" +++ "you run this command! You will be prompted for confirmation before\n" +++ "this takes place.\n" +++ "\n" +++ "This command was historically called `resolve', and this deprecated\n" +++ "alias still exists for backwards-compatibility.\n"++markconflicts :: DarcsCommand+markconflicts = DarcsCommand {command_name = "mark-conflicts",+ command_help = markconflicts_help,+ command_description = markconflicts_description,+ command_extra_args = 0,+ command_extra_arg_help = [],+ command_command = markconflicts_cmd,+ command_prereq = amInRepository,+ command_get_arg_possibilities = return [],+ command_argdefaults = nodefaults,+ command_advanced_options = [umask_option],+ command_basic_options = [ignoretimes,+ working_repo_dir]}++markconflicts_cmd :: [DarcsFlag] -> [String] -> IO ()+markconflicts_cmd opts [] = withRepoLock opts $- \repository -> do+ pend <- get_unrecorded_unsorted repository+ r <- read_repo repository+ Sealed res <- return $ patchset_conflict_resolutions r+ case res of NilFL -> do putStrLn "No conflicts to mark."+ exitWith ExitSuccess+ _ -> return ()+ case pend of+ NilFL -> return ()+ _ -> do yorn <- promptYorn+ ("This will trash any unrecorded changes"+++ " in the working directory.\nAre you sure? ")+ when (yorn /= 'y') $ exitWith ExitSuccess+ applyToWorking repository opts (invert pend) `catch` \e ->+ bug ("Can't undo pending changes!" ++ show e)+ sync_repo repository+ withSignalsBlocked $+ do add_to_pending repository res+ applyToWorking repository opts res `catch` \e ->+ bug ("Problem marking conflicts in mark-conflicts!" ++ show e)+ putStrLn "Finished marking conflicts."+markconflicts_cmd _ _ = impossible++-- |resolve is an alias for mark-conflicts.+resolve :: DarcsCommand+resolve = command_alias "resolve" markconflicts+\end{code}
@@ -0,0 +1,186 @@+% Copyright (C) 2002-2003 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\darcsCommand{move}+\begin{code}+{-# OPTIONS_GHC -cpp #-}+{-# LANGUAGE CPP #-}++module Darcs.Commands.Move ( move, mv ) where+import Control.Monad ( when, unless, zipWithM_ )+import Data.Maybe ( catMaybes )+import Darcs.SignalHandler ( withSignalsBlocked )++import Darcs.Commands ( DarcsCommand(..), nodefaults, command_alias )+import Darcs.Arguments ( DarcsFlag( AllowCaseOnly, AllowWindowsReserved ),+ fixSubPaths, working_repo_dir,+ list_files, allow_problematic_filenames, umask_option,+ )+import Darcs.RepoPath ( toFilePath, sp2fn )+import System.FilePath.Posix ( (</>), takeFileName )+import System.Directory ( renameDirectory )+import Workaround ( renameFile )+import Darcs.Repository ( Repository, withRepoLock, ($-), amInRepository,+ slurp_pending, add_to_pending,+ )+import Darcs.Ordered ( FL(..), unsafeFL )+import Darcs.Global ( debugMessage )+import qualified Darcs.Patch+import Darcs.Patch ( RepoPatch, Prim )+import Darcs.SlurpDirectory ( Slurpy, slurp, slurp_has, slurp_has_anycase,+ slurp_remove, slurp_hasdir, slurp_hasfile )+import Darcs.Patch.FileName ( fp2fn, fn2fp, super_name )+import qualified System.FilePath.Windows as WindowsFilePath++import Darcs.Gorsvet( invalidateIndex )+#include "impossible.h"++move_description :: String+move_description = "Move or rename files."++move_help :: String+move_help =+ "Darcs cannot reliably distinguish between a file being deleted and a\n" +++ "new one added, and a file being moved. Therefore Darcs always assumes\n" +++ "the former, and provides the `darcs mv' command to let Darcs know when\n" +++ "you want the latter. This command will also move the file in the\n" +++ "working tree (unlike `darcs remove').\n" +++ "\n" +++ -- Note that this paragraph is very similar to one in ./Add.lhs.+ "Darcs will not rename a file if another file in the same folder has\n" +++ "the same name, except for case. The --case-ok option overrides this\n" +++ "behaviour. Windows and OS X usually use filesystems that do not allow\n" +++ "files a folder to have the same name except for case (for example,\n" +++ "`ReadMe' and `README'). If --case-ok is used, the repository might be\n" +++ "unusable on those systems!\n"++move :: DarcsCommand+move = DarcsCommand {command_name = "move",+ command_help = move_help,+ command_description = move_description,+ command_extra_args = -1,+ command_extra_arg_help = ["<SOURCE> ... <DESTINATION>"],+ command_command = move_cmd,+ command_prereq = amInRepository,+ command_get_arg_possibilities = list_files,+ command_argdefaults = nodefaults,+ command_advanced_options = [umask_option],+ command_basic_options = [allow_problematic_filenames, working_repo_dir]}+move_cmd :: [DarcsFlag] -> [String] -> IO ()+move_cmd _ [] = fail "The `darcs move' command requires at least two arguments."+move_cmd _ [_] = fail "The `darcs move' command requires at least two arguments."++move_cmd opts args@[_,_] = withRepoLock opts $- \repository -> do+ two_files <- fixSubPaths opts args+ [old,new] <- return $ case two_files of+ [_,_] -> two_files+ [_] -> error "Cannot rename a file or directory onto itself!"+ xs -> bug $ "Problem in move_cmd: " ++ show xs+ work <- slurp "."+ let old_fp = toFilePath old+ new_fp = toFilePath new+ if slurp_hasdir (sp2fn new) work && slurp_has old_fp work+ then move_to_dir repository opts [old_fp] new_fp+ else do+ cur <- slurp_pending repository+ addpatch <- check_new_and_old_filenames opts cur work (old_fp,new_fp)+ invalidateIndex repository+ withSignalsBlocked $ do+ case addpatch of+ Nothing -> add_to_pending repository (Darcs.Patch.move old_fp new_fp :>: NilFL)+ Just p -> add_to_pending repository (p :>: Darcs.Patch.move old_fp new_fp :>: NilFL)+ move_file_or_dir work old_fp new_fp++move_cmd opts args =+ withRepoLock opts $- \repository -> do+ relpaths <- map toFilePath `fmap` fixSubPaths opts args+ let moved = init relpaths+ finaldir = last relpaths+ move_to_dir repository opts moved finaldir++move_to_dir :: RepoPatch p => Repository p -> [DarcsFlag] -> [FilePath] -> FilePath -> IO ()+move_to_dir repository opts moved finaldir =+ let movefns = map takeFileName moved+ movetargets = map (finaldir </>) movefns+ movepatches = zipWith Darcs.Patch.move moved movetargets+ in do+ cur <- slurp_pending repository+ work <- slurp "."+ addpatches <- mapM (check_new_and_old_filenames opts cur work) $ zip moved movetargets+ invalidateIndex repository+ withSignalsBlocked $ do+ add_to_pending repository $ unsafeFL $ catMaybes addpatches ++ movepatches+ zipWithM_ (move_file_or_dir work) moved movetargets++check_new_and_old_filenames+ :: [DarcsFlag] -> Slurpy -> Slurpy -> (FilePath, FilePath) -> IO (Maybe Prim)+check_new_and_old_filenames opts cur work (old,new) = do+ unless (AllowWindowsReserved `elem` opts || WindowsFilePath.isValid new) $+ fail $ "The filename " ++ new ++ " is not valid under Windows.\n" +++ "Use --reserved-ok to allow such filenames."+ maybe_add_file_thats_been_moved <-+ if slurp_has old work -- We need to move the object+ then do unless (slurp_hasdir (super_name $ fp2fn new) work) $+ fail $ "The target directory " +++ (fn2fp $ super_name $ fp2fn new)+++ " isn't known in working directory, did you forget to add it?"+ when (it_has new work) $ fail $ already_exists "working directory"+ return Nothing+ else do unless (slurp_has new work) $ fail $ doesnt_exist "working directory"+ return $ Just $ Darcs.Patch.addfile old+ if slurp_has old cur+ then do unless (slurp_hasdir (super_name $ fp2fn new) cur) $+ fail $ "The target directory " +++ (fn2fp $ super_name $ fp2fn new)+++ " isn't known in working directory, did you forget to add it?"+ when (it_has new cur) $ fail $ already_exists "repository"+ else fail $ doesnt_exist "repository"+ return maybe_add_file_thats_been_moved+ where it_has f s = + let ms2 = slurp_remove (fp2fn old) s+ in case ms2 of+ Nothing -> False+ Just s2 -> if AllowCaseOnly `elem` opts + then slurp_has f s2+ else slurp_has_anycase f s2+ already_exists what_slurpy =+ if AllowCaseOnly `elem` opts+ then "A file or dir named "++new++" already exists in "+ ++ what_slurpy ++ "."+ else "A file or dir named "++new++" (or perhaps differing"+++ " only in case)\nalready exists in "+++ what_slurpy ++ ".\n"+++ "Use --case-ok to allow files differing only in case."+ doesnt_exist what_slurpy =+ "There is no file or dir named " ++ old +++ " in the "++ what_slurpy ++ "."++move_file_or_dir :: Slurpy -> FilePath -> FilePath -> IO ()+move_file_or_dir work old new =+ if slurp_hasfile (fp2fn old) work+ then do debugMessage $ unwords ["renameFile",old,new]+ renameFile old new+ else if slurp_hasdir (fp2fn old) work+ then do debugMessage $ unwords ["renameDirectory",old,new]+ renameDirectory old new+ else return ()++mv :: DarcsCommand+mv = command_alias "mv" move+\end{code}++
@@ -0,0 +1,314 @@+% Copyright (C) 2003-2005 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\darcsCommand{optimize}+\begin{code}+{-# OPTIONS_GHC -cpp #-}+{-# LANGUAGE CPP #-}++module Darcs.Commands.Optimize ( optimize ) where+import Control.Monad ( when, unless )+import Data.Maybe ( isJust )+import Text.Regex ( mkRegex, matchRegex )+import System.Directory ( getDirectoryContents, doesDirectoryExist )++import Darcs.Hopefully ( hopefully, info )+import Darcs.Commands ( DarcsCommand(..), nodefaults )+import Darcs.Arguments ( DarcsFlag( Compress, UnCompress,+ NoCompress, Reorder,+ TagName, CheckPoint,+ Relink, RelinkPristine ),+ tagname, checkpoint, reorder_patches,+ uncompress_nocompress,+ relink, relink_pristine, sibling,+ flagsToSiblings,+ working_repo_dir, umask_option,+ )+import Darcs.Repository.Prefs ( get_preflist )+import Darcs.Repository ( Repository, PatchSet, withRepoLock, ($-), withGutsOf,+ read_repo, optimizeInventory, slurp_recorded,+ tentativelyReplacePatches, cleanRepository,+ amInRepository, finalizeRepositoryChanges )+import Darcs.Repository.Checkpoint ( write_checkpoint )+import Darcs.Ordered ( RL(..), unsafeUnRL, (+<+), mapFL_FL, reverseRL, mapRL, concatRL )+import Darcs.Patch.Info ( PatchInfo, just_name, human_friendly )+import Darcs.Patch ( RepoPatch )+import ByteStringUtils ( gzReadFilePS )+import Darcs.Patch.Depends ( deep_optimize_patchset, slightly_optimize_patchset,+ get_patches_beyond_tag, get_patches_in_tag,+ )+import Darcs.Lock ( maybeRelink, gzWriteAtomicFilePS, writeAtomicFilePS )+import Darcs.RepoPath ( toFilePath )+import Darcs.Utils ( withCurrentDirectory )+import Progress ( debugMessage )+import Printer ( putDocLn, text, ($$) )+import Darcs.SlurpDirectory ( slurp, list_slurpy_files )+import Darcs.Repository.Pristine ( identifyPristine, pristineDirectory )+import Darcs.Sealed ( FlippedSeal(..), unsafeUnseal )+import Darcs.Global ( darcsdir )+#include "impossible.h"++optimize_description :: String+optimize_description = "Optimize the repository."++optimize_help :: String+optimize_help =+ "The `darcs optimize' command modifies the current repository in an\n" +++ "attempt to reduce its resource requirements. By default a single\n" +++ "fast, safe optimization is performed; additional optimization\n" +++ "techniques can be enabled by passing options to `darcs optimize'.\n" +++ "\n" ++ optimize_help_inventory +++ -- "\n" ++ optimize_help_reorder +++ "\n" ++ optimize_help_relink +++ -- checkpoints and uncompression are least useful, so they are last.+ "\n" ++ optimize_help_compression +++ "\n" ++ optimize_help_checkpoint +++ "\n" +++ "There is one more optimization which CAN NOT be performed by this\n" +++ "command. Every time your record a patch, a new inventory file is\n" +++ "written to _darcs/inventories/, and old inventories are never reaped.\n" +++ "\n" +++ "If _darcs/inventories/ is consuming a relatively large amount of\n" +++ "space, you can safely reclaim it by using `darcs get' to make a\n" +++ "complete copy of the repo. When doing so, don't forget to copy over\n" +++ "any unsaved changes you have made to the working tree or to\n" +++ "unversioned files in _darcs/prefs/ (such as _darcs/prefs/author).\n"++optimize :: DarcsCommand+optimize = DarcsCommand {command_name = "optimize",+ command_help = optimize_help,+ command_description = optimize_description,+ command_extra_args = 0,+ command_extra_arg_help = [],+ command_command = optimize_cmd,+ command_prereq = amInRepository,+ command_get_arg_possibilities = return [],+ command_argdefaults = nodefaults,+ command_advanced_options = [uncompress_nocompress, umask_option],+ command_basic_options = [checkpoint,+ tagname,+ working_repo_dir,+ reorder_patches,+ sibling, relink,+ relink_pristine]}++optimize_cmd :: [DarcsFlag] -> [String] -> IO ()+optimize_cmd origopts _ = withRepoLock opts $- \repository -> do+ cleanRepository repository+ do_reorder opts repository+ do_optimize_inventory repository+ when (CheckPoint `elem` opts) $ do_checkpoint opts repository+ when (Compress `elem` opts || UnCompress `elem` opts) $ optimize_compression opts+ when (Relink `elem` opts || (RelinkPristine `elem` opts)) $+ do_relink opts repository+ putStrLn "Done optimizing!"+ where opts = if UnCompress `elem` origopts then NoCompress:origopts else origopts+is_tag :: PatchInfo -> Bool+is_tag pinfo = take 4 (just_name pinfo) == "TAG "++optimize_help_inventory :: String+optimize_help_inventory =+ "The default optimization moves recent patches (those not included in\n" +++ "the latest tag) to the `front', reducing the amount that a typical\n" +++ "remote command needs to download. It should also reduce the CPU time\n" +++ "needed for some operations.\n"++do_optimize_inventory :: RepoPatch p => Repository p -> IO ()+do_optimize_inventory repository = do+ debugMessage "Writing out a nice copy of the inventory."+ optimizeInventory repository+ debugMessage "Done writing out a nice copy of the inventory."++optimize_help_checkpoint :: String+optimize_help_checkpoint =+ "If the repository is in `old-fashioned-inventory' format, the `darcs\n" +++ "optimize --checkpoint' command creates a checkpoint of the latest tag.\n" +++ "This checkpoint is used by `darcs get --partial' to create partial\n" +++ "repositories. With the `--tag' option, checkpoints for older tags can\n" +++ "be created. In newer repository formats, this feature has been\n" +++ "replaced by `darcs get --lazy', which does not require checkpoints.\n"++do_checkpoint :: RepoPatch p => [DarcsFlag] -> Repository p -> IO ()+do_checkpoint opts repository = do+ mpi <- get_tag opts repository+ case mpi of+ Nothing -> return ()+ Just pinfo -> do putDocLn $ text "Checkpointing tag:"+ $$ human_friendly pinfo+ write_checkpoint repository pinfo++get_tag :: RepoPatch p => [DarcsFlag] -> Repository p -> IO (Maybe PatchInfo)+get_tag [] r = do ps <- read_repo r+ case filter is_tag $ lasts $ mapRL (mapRL info) ps of+ [] -> do putStrLn "There is no tag to checkpoint!"+ return Nothing+ (pinfo:_) -> return $ Just pinfo+get_tag (TagName t:_) r =+ do ps <- read_repo r+ case filter (match_tag t) $ lasts $ mapRL (mapRL info) ps of+ (pinfo:_) -> return $ Just pinfo+ _ -> case filter (match_tag t) $+ lasts $ mapRL (mapRL info) $ deep_optimize_patchset ps of+ (pinfo:_) -> return $ Just pinfo+ _ -> do putStr "Cannot checkpoint any tag "+ putStr $ "matching '"++t++"'\n"+ return Nothing+get_tag (_:fs) r = get_tag fs r++lasts :: [[a]] -> [a]+lasts [] = []+lasts (x@(_:_):ls) = last x : lasts ls+lasts ([]:ls) = lasts ls++mymatch :: String -> PatchInfo -> Bool+mymatch r = match_name $ matchRegex (mkRegex r)+match_name :: (String -> Maybe a) -> PatchInfo -> Bool+match_name ch pinfo = isJust $ ch (just_name pinfo)+match_tag :: String -> PatchInfo -> Bool+match_tag ('^':n) = mymatch $ "^TAG "++n+match_tag n = mymatch $ "^TAG .*"++n+++optimize_help_compression :: String+optimize_help_compression =+ "By default patches are compressed with zlib (RFC 1951) to reduce\n" +++ "storage (and download) size. In exceptional circumstances, it may be\n" +++ "preferable to avoid compression. In this case the `--dont-compress'\n" +++ "option can be used (e.g. with `darcs record') to avoid compression.\n" +++ "\n" +++ "The `darcs optimize --uncompress' and `darcs optimize --compress'\n" +++ "commands can be used to ensure existing patches in the current\n" +++ "repository are respectively uncompressed or compressed. Note that\n" +++ "repositories in the legacy `old-fashioned-inventory' format have a .gz\n" +++ "extension on patch files even when uncompressed.\n"++optimize_compression :: [DarcsFlag] -> IO ()+optimize_compression opts = do+ putStrLn "Optimizing (un)compression of patches..."+ do_compress (darcsdir++"/patches")+ putStrLn "Optimizing (un)compression of inventories..."+ do_compress (darcsdir++"/inventories")+ where do_compress f =+ do isd <- doesDirectoryExist f+ if isd then withCurrentDirectory f $+ do fs <- filter notdot `fmap` getDirectoryContents "."+ mapM_ do_compress fs+ else if Compress `elem` opts+ then gzReadFilePS f >>= gzWriteAtomicFilePS f+ else gzReadFilePS f >>= writeAtomicFilePS f+ notdot ('.':_) = False+ notdot _ = True++optimize_help_relink :: String+optimize_help_relink =+ "The `darcs optimize --relink' command hard-links patches that the\n" +++ "current repository has in common with its peers. Peers are those\n" +++ "repositories listed in _darcs/prefs/sources, or defined with the\n" +++ "`--sibling' option (which can be used multiple times).\n" +++ "\n" +++ "Darcs uses hard-links automatically, so this command is rarely needed.\n" +++ "It is most useful if you used `cp -r' instead of `darcs get' to copy a\n" +++ "repository, or if you pulled the same patch from a remote repository\n" +++ "into multiple local repositories.\n" +++ "\n" +++ "A `darcs optimize --relink-pristine' command is also available, but\n" +++ "generally SHOULD NOT be used. It results in a relatively small space\n" +++ "saving at the cost of making many Darcs commands MUCH slower.\n"++do_relink :: RepoPatch p => [DarcsFlag] -> Repository p -> IO ()+do_relink opts repository =+ do some_siblings <- return (flagsToSiblings opts)+ defrepolist <- get_preflist "defaultrepo"+ siblings <- return (map toFilePath some_siblings ++ defrepolist)+ if (siblings == []) + then putStrLn "No siblings -- no relinking done."+ else do when (Relink `elem` opts) $+ do debugMessage "Relinking patches..."+ patches <-+ (fmap list_slurpy_files) (slurp $ darcsdir++"/patches")+ maybeRelinkFiles siblings patches (darcsdir++"/patches")+ when (RelinkPristine `elem` opts) $+ do pristine <- identifyPristine+ case (pristineDirectory pristine) of+ (Just d) -> do+ debugMessage "Relinking pristine tree..."+ c <- slurp_recorded repository+ maybeRelinkFiles+ siblings (list_slurpy_files c) d+ Nothing -> return ()+ debugMessage "Done relinking."+ return ()+ return ()++maybeRelinkFiles :: [String] -> [String] -> String -> IO ()+maybeRelinkFiles src dst dir = + mapM_ (maybeRelinkFile src) (map ((dir ++ "/") ++) dst)++maybeRelinkFile :: [String] -> String -> IO ()+maybeRelinkFile [] _ = return ()+maybeRelinkFile (h:t) f =+ do done <- maybeRelink (h ++ "/" ++ f) f+ unless done $+ maybeRelinkFile t f+ return ()+++\end{code}+\begin{options}+--reorder-patches+\end{options}++The \verb|--reorder-patches| option causes Darcs to create an optimal+ordering of its internal patch inventory. This may help to produce shorter+`context' lists when sending patches, and may improve performance for some+other operations as well. You should not run \verb!--reorder-patches! on a+repository from which someone may be simultaneously pulling or getting, as+this could lead to repository corruption.+\begin{code}++-- FIXME: someone needs to grovel through the source and determine+-- just how optimizeInventory differs from do_reorder. The following+-- is purely speculation. --twb, 2009-04+-- optimize_help_reorder :: String+-- optimize_help_reorder =+-- "The `darcs optimize --reorder' command is a more comprehensive version\n" +++-- "of the default optimization. It reorders patches with respect to ALL\n" +++-- "tags, rather than just the latest tag.\n"++do_reorder :: RepoPatch p => [DarcsFlag] -> Repository p -> IO ()+do_reorder opts _ | not (Reorder `elem` opts) = return ()+do_reorder opts repository = do+ debugMessage "Reordering the inventory."+ psnew <- choose_order `fmap` read_repo repository+ let ps = mapFL_FL hopefully $ reverseRL $ head $ unsafeUnRL psnew+ withGutsOf repository $ do tentativelyReplacePatches repository opts ps+ finalizeRepositoryChanges repository+ debugMessage "Done reordering the inventory."++choose_order :: RepoPatch p => PatchSet p -> PatchSet p+choose_order ps | isJust last_tag =+ case slightly_optimize_patchset $ unsafeUnseal $ get_patches_in_tag lt ps of + ((t:<:NilRL):<:pps) -> case get_patches_beyond_tag lt ps of+ FlippedSeal (p :<: NilRL) -> (p+<+(t:<:NilRL)) :<: pps+ _ -> impossible+ _ -> impossible + where last_tag = case filter is_tag $ mapRL info $ concatRL ps of+ (t:_) -> Just t+ _ -> Nothing+ lt = fromJust last_tag+choose_order ps = ps+\end{code}
@@ -0,0 +1,274 @@+% Copyright (C) 2002-2005 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\darcsCommand{pull}+\begin{code}+{-# OPTIONS_GHC -cpp #-}+{-# LANGUAGE CPP #-}++module Darcs.Commands.Pull ( pull ) where+import System.Exit ( ExitCode(..), exitWith )+import Workaround ( getCurrentDirectory )+import Control.Monad ( when )+import Data.List ( nub )++import Darcs.Commands ( DarcsCommand(..), loggers )+import Darcs.CommandsAux ( check_paths )+import Darcs.Arguments ( DarcsFlag( Verbose, Quiet, DryRun, MarkConflicts, XMLOutput,+ Intersection, Complement, AllowConflicts, NoAllowConflicts ),+ nocompress, ignoretimes, definePatches,+ deps_sel, pull_conflict_options, use_external_merge,+ match_several, fixUrl,+ all_interactive, repo_combinator,+ print_dry_run_message_and_exit,+ test, dry_run,+ set_default, summary, working_repo_dir, remote_repo,+ set_scripts_executable, nolinks,+ network_options, umask_option, allow_unrelated_repos, restrict_paths+ )+import Darcs.Repository ( Repository, SealedPatchSet, identifyRepositoryFor, withGutsOf,+ amInRepository, withRepoLock, ($-), tentativelyMergePatches,+ sync_repo, finalizeRepositoryChanges, applyToWorking,+ read_repo, checkUnrelatedRepos )+import Darcs.Hopefully ( info )+import Darcs.Patch ( RepoPatch, description )+import Darcs.Ordered ( (:>)(..), (:\/:)(..), RL(..), unsafeUnRL, concatRL,+ mapFL, nullFL, reverseRL, mapRL )+import Darcs.Patch.Permutations ( partitionFL )+import Darcs.SlurpDirectory ( wait_a_moment )+import Darcs.Repository.Prefs ( add_to_preflist, defaultrepo, set_defaultrepo, get_preflist )+import Darcs.Repository.Motd (show_motd )+import Darcs.Patch.Depends ( get_common_and_uncommon,+ patchset_intersection, patchset_union )+import Darcs.SelectChanges ( with_selected_changes )+import Darcs.Utils ( clarify_errors, formatPath )+import Darcs.Sealed ( Sealed(..), seal )+import Printer ( putDocLn, vcat, ($$), text )+import Darcs.Gorsvet( invalidateIndex )+#include "impossible.h"++pull_description :: String+pull_description =+ "Copy and apply patches from another repository to this one."++pull_help :: String+pull_help =+ "Pull is used to bring changes made in another repository into the current\n"+++ "repository (that is, either the one in the current directory, or the one\n"+++ "specified with the --repodir option). Pull allows you to bring over all or\n"+++ "some of the patches that are in that repository but not in this one. Pull\n"+++ "accepts arguments, which are URLs from which to pull, and when called\n"+++ "without an argument, pull will use the repository from which you have most\n"+++ "recently either pushed or pulled.\n"++pull :: DarcsCommand+pull = DarcsCommand {command_name = "pull",+ command_help = pull_help,+ command_description = pull_description,+ command_extra_args = -1,+ command_extra_arg_help = ["[REPOSITORY]..."],+ command_command = pull_cmd,+ command_prereq = amInRepository,+ command_get_arg_possibilities = get_preflist "repos",+ command_argdefaults = defaultrepo,+ command_advanced_options = [repo_combinator,+ nocompress, nolinks,+ ignoretimes,+ remote_repo,+ set_scripts_executable,+ umask_option,+ restrict_paths] +++ network_options,+ command_basic_options = [match_several,+ all_interactive,+ pull_conflict_options,+ use_external_merge,+ test]++dry_run++[summary,+ deps_sel,+ set_default,+ working_repo_dir,+ allow_unrelated_repos]}++pull_cmd :: [DarcsFlag] -> [String] -> IO ()++pull_cmd opts unfixedrepodirs@(_:_) =+ let (logMessage, _, logDocLn) = loggers opts+ putInfo = if (Quiet `elem` opts || XMLOutput `elem` opts) then \_ -> return () else logDocLn+ putVerbose = if Verbose `elem` opts then putDocLn else \_ -> return ()+ in withRepoLock opts $- \repository -> do+ here <- getCurrentDirectory+ repodirs <- (nub . filter (/= here)) `fmap` mapM (fixUrl opts) unfixedrepodirs+ -- Test to make sure we aren't trying to pull from the current repo+ when (null repodirs) $+ fail "Can't pull from current repository!"+ (Sealed them, Sealed compl) <- read_repos repository opts repodirs+ old_default <- get_preflist "defaultrepo"+ set_defaultrepo (head repodirs) opts+ mapM_ (add_to_preflist "repos") repodirs+ when (old_default == repodirs) $+ let pulling = if DryRun `elem` opts then "Would pull" else "Pulling"+ in putInfo $ text $ pulling++" from "++concatMap formatPath repodirs++"..."+ mapM_ (show_motd opts) repodirs+ us <- read_repo repository+ (common, us' :\/: them'') <- return $ get_common_and_uncommon (us, them)+ (_ , _ :\/: compl') <- return $ get_common_and_uncommon (us, compl)+ checkUnrelatedRepos opts common us them+ let avoided = mapRL info (concatRL compl')+ ps :> _ <- return $ partitionFL (not . (`elem` avoided) . info) $ reverseRL $ concatRL them''+ do when (Verbose `elem` opts) $+ do case us' of+ (x@(_:<:_):<:_) -> putDocLn $ text "We have the following new (to them) patches:"+ $$ (vcat $ mapRL description x)+ _ -> return ()+ when (not $ nullFL ps) $ putDocLn $ text "They have the following patches to pull:"+ $$ (vcat $ mapFL description ps)+ when (nullFL ps) $ do putInfo $ text "No remote changes to pull in!"+ definePatches ps+ exitWith ExitSuccess+ with_selected_changes "pull" opts ps $+ \ (to_be_pulled:>_) -> do+ print_dry_run_message_and_exit "pull" opts to_be_pulled+ definePatches to_be_pulled+ when (nullFL to_be_pulled) $ do+ logMessage "You don't want to pull any patches, and that's fine with me!"+ exitWith ExitSuccess+ check_paths opts to_be_pulled+ putVerbose $ text "Getting and merging the following patches:"+ putVerbose $ vcat $ mapFL description to_be_pulled+ let merge_opts | NoAllowConflicts `elem` opts = opts+ | AllowConflicts `elem` opts = opts+ | otherwise = MarkConflicts : opts+ Sealed pw <- tentativelyMergePatches repository "pull" merge_opts+ (reverseRL $ head $ unsafeUnRL us') to_be_pulled+ invalidateIndex repository+ withGutsOf repository $ do finalizeRepositoryChanges repository+ -- so work will be more recent than rec:+ revertable $ do wait_a_moment+ applyToWorking repository opts pw+ sync_repo repository+ putInfo $ text "Finished pulling and applying."+ where revertable x = x `clarify_errors` unlines+ ["Error applying patch to the working directory.","",+ "This may have left your working directory an inconsistent",+ "but recoverable state. If you had no un-recorded changes",+ "by using 'darcs revert' you should be able to make your",+ "working directory consistent again."]+pull_cmd _ [] = fail "No default repository to pull from, please specify one"++{- Read in the specified pull-from repositories. Perform+Intersection, Union, or Complement read. In patch-theory terms+(stated in set algebra, where + is union and & is intersection+and \ is complement):++ Union = ((R1 + R2 + ... + Rn) \ Rc)+ Intersection = ((R1 & R2 & ... & Rn) \ Rc)+ Complement = (R1 \ Rc) \ ((R2 + R3 + ... + Rn) \ Rc)++ where Rc = local repo+ R1 = 1st specified pull repo+ R2, R3, Rn = other specified pull repo++Since Rc is not provided here yet, the result of read_repos is a+tuple: the first patchset(s) to be complemented against Rc and then+the second patchset(s) to be complemented against Rc.+-}++read_repos :: RepoPatch p => Repository p -> [DarcsFlag] -> [String] -> IO (SealedPatchSet p,SealedPatchSet p)+read_repos _ _ [] = impossible+read_repos to_repo opts us =+ do rs <- mapM (\u -> do r <- identifyRepositoryFor to_repo u+ ps <- read_repo r+ return $ seal ps) us+ return $ if Intersection `elem` opts+ then (patchset_intersection rs, seal NilRL)+ else if Complement `elem` opts+ then (head rs, patchset_union $ tail rs)+ else (patchset_union rs, seal NilRL)++\end{code}++\begin{options}+--intersection, --union [DEFAULT], --complement+\end{options}++If you provide more than one repository as an argument to pull, darcs'+behavior is determined by the presence of the \verb!--complement!,+\verb!--intersection!, and \verb!--union! flags. ++\begin{itemize}++\item The default (\verb!--union!) behavior is to pull any patches+that are in any of the specified repositories ($ R_1 \bigcup R_2+\bigcup R_3 \ldots$).++\item If you instead specify the \verb!--intersection! flag, darcs+will only pull those patches which are present in all source+repositories ($ R_1 \bigcap R_2 \bigcap R_3 \ldots$).++\item If you specify the \verb!--complement! flag, darcs will only+pull elements in the first repository that do not exist in any of the+remaining repositories\footnote{The first thing darcs will do is+remove duplicates, keeping only the first specification. This is+noticeable for the complement operation, since mathematically $ S+\backslash S \rightarrow \emptyset $, one would expect that+``\texttt{darcs pull --complement repo1 repo1}'' would result in no+pulls, but the duplicate elimination removes the second+\texttt{repo1}, reducing the above to effectively ``\texttt{darcs pull+repo1}''. The expected functionality could be seen via+``\texttt{darcs get -a repo1 repo2; darcs pull --complement repo1+repo2}'', but there are easier ways of doing nothing!} ($ R_1+\backslash (R_2 \bigcup R_3 \bigcup \ldots$)).++\end{itemize}+++\begin{options}+--external-merge+\end{options}++You can use an external interactive merge tool to resolve conflicts with the+flag \verb!--external-merge!. For more details see+subsection~\ref{resolution}.++\begin{options}+--matches, --patches, --tags, --no-deps+\end{options}++The \verb!--patches!, \verb!--matches!, \verb!--tags!, and \verb!--no-deps!+options can be used to select which patches to pull, as described in+subsection~\ref{selecting}.++\begin{options}+--no-test, --test+\end{options}++If you specify the \verb!--test! option, pull will run the test (if a test+exists) on a scratch copy of the repository contents prior to actually performing+the pull. If the test fails, the pull will be aborted.++\begin{options}+--verbose+\end{options}++Adding the \verb!--verbose! option causes another section to appear in the+output which also displays a summary of patches that you have and the remote+repository lacks. Thus, the following syntax can be used to show you all the patch+differences between two repositories:++\begin{verbatim}+darcs pull --dry-run --verbose+\end{verbatim}
@@ -0,0 +1,255 @@+% Copyright (C) 2002-2004 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\darcsCommand{push}+\begin{code}+{-# OPTIONS_GHC -cpp #-}+{-# LANGUAGE CPP #-}++module Darcs.Commands.Push ( push ) where+import System.Exit ( exitWith, ExitCode( ExitSuccess, ExitFailure ) )+import Control.Monad ( when )+import Data.Char ( toUpper )+import Workaround ( getCurrentDirectory )+import Darcs.Commands ( DarcsCommand(..) )+import Darcs.Arguments ( DarcsFlag( DryRun, Verbose, Quiet, Sign, SignAs, NoSign, SignSSL ),+ definePatches,+ working_repo_dir, summary,+ print_dry_run_message_and_exit,+ applyas, match_several, fixUrl, deps_sel,+ all_interactive, dry_run, nolinks,+ remote_repo, network_options,+ set_default, sign, allow_unrelated_repos+ )+import Darcs.Hopefully ( hopefully )+import Darcs.Repository ( withRepoReadLock, ($-), identifyRepositoryFor,+ read_repo, amInRepository, checkUnrelatedRepos )+import Darcs.Patch ( description )+import Darcs.Ordered ( RL(..), (:>)(..), (:\/:)(..),+ nullFL, reverseRL, mapFL_FL, unsafeUnRL, mapRL, lengthRL )+import Darcs.Repository.Prefs ( defaultrepo, set_defaultrepo, get_preflist )+import Darcs.External ( maybeURLCmd, signString )+import Darcs.URL ( is_url, is_file )+import Darcs.SelectChanges ( with_selected_changes )+import Darcs.Utils ( formatPath )+import Darcs.Patch.Depends ( get_common_and_uncommon )+import Darcs.Patch.Bundle ( make_bundle )+import Printer ( vcat, empty, text, ($$), (<+>), putDocLn, errorDoc )+import Darcs.RemoteApply ( remote_apply, apply_as )+import Darcs.Email ( make_email )+import English (englishNum, Noun(..))+#include "impossible.h"++push_description :: String+push_description =+ "Copy and apply patches from this repository to another one."++push_help :: String+push_help =+ "Push is the opposite of pull. Push allows you to copy changes from the\n"+++ "current repository into another repository.\n"++push :: DarcsCommand+push = DarcsCommand {command_name = "push",+ command_help = push_help,+ command_description = push_description,+ command_extra_args = 1,+ command_extra_arg_help = ["[REPOSITORY]"],+ command_command = push_cmd,+ command_prereq = amInRepository,+ command_get_arg_possibilities = get_preflist "repos",+ command_argdefaults = defaultrepo,+ command_advanced_options = [applyas,+ nolinks,+ remote_repo] +++ network_options,+ command_basic_options = [match_several, deps_sel,+ all_interactive,+ sign]++dry_run++[summary,+ working_repo_dir,+ set_default,+ allow_unrelated_repos]}++push_cmd :: [DarcsFlag] -> [String] -> IO ()+push_cmd opts [""] = push_cmd opts []+push_cmd opts [unfixedrepodir] =+ let am_verbose = Verbose `elem` opts+ am_quiet = Quiet `elem` opts+ putVerbose s = when am_verbose $ putDocLn s+ putInfo s = when (not am_quiet) $ putDocLn s+ in+ do+ repodir <- fixUrl opts unfixedrepodir+ -- Test to make sure we aren't trying to push to the current repo+ here <- getCurrentDirectory+ when (repodir == here) $+ fail "Cannot push from repository to itself."+ -- absolute '.' also taken into account by fix_filepath+ (bundle,num_to_pull) <- withRepoReadLock opts $- \repository -> do+ if is_url repodir then do+ when (apply_as opts /= Nothing) $+ let msg = text "Cannot --apply-as when pushing to URLs" in+ if DryRun `elem` opts+ then putInfo $ text "NOTE: " <+> msg+ $$ text ""+ else errorDoc msg+ maybeapply <- maybeURLCmd "APPLY" repodir+ when (maybeapply == Nothing) $+ let lprot = takeWhile (/= ':') repodir+ prot = map toUpper lprot+ msg = text ("Pushing to "++lprot++" URLs is not supported.\n"+++ "You may be able to hack this to work"+++ " using DARCS_APPLY_"++prot) in+ if DryRun `elem` opts+ then putInfo $ text "NOTE:" <+> msg+ $$ text ""+ else errorDoc msg+ else do+ when (want_sign opts) $+ let msg = text "Signing doesn't make sense for local repositories or when pushing over ssh."+ in if DryRun `elem` opts+ then putInfo $ text "NOTE:" <+> msg+ else errorDoc msg+ them <- identifyRepositoryFor repository repodir >>= read_repo+ old_default <- get_preflist "defaultrepo"+ set_defaultrepo repodir opts+ when (old_default == [repodir]) $+ let pushing = if DryRun `elem` opts then "Would push" else "Pushing"+ in putInfo $ text $ pushing++" to "++formatPath repodir++"..."+ us <- read_repo repository+ case get_common_and_uncommon (us, them) of+ (common, us' :\/: them') -> do+ checkUnrelatedRepos opts common us them+ putVerbose $ text "We have the following patches to push:"+ $$ (vcat $ mapRL description $ head $ unsafeUnRL us')+ firstUs <- case us' of+ NilRL:<:NilRL -> do putInfo $ text "No recorded local changes to push!"+ exitWith ExitSuccess+ NilRL -> bug "push_cmd: us' is empty!"+ (x:<:_) -> return x+ with_selected_changes "push" opts (reverseRL firstUs) $+ \ (to_be_pushed:>_) -> do+ definePatches to_be_pushed+ print_dry_run_message_and_exit "push" opts to_be_pushed+ when (nullFL to_be_pushed) $ do+ putInfo $+ text "You don't want to push any patches, and that's fine with me!"+ exitWith ExitSuccess+ let num_to_pull = lengthRL $ head $ unsafeUnRL them'+ bundle = make_bundle []+ (bug "using slurpy in make_bundle called from Push")+ common (mapFL_FL hopefully to_be_pushed)+ return (bundle, num_to_pull)+ sbundle <- signString opts bundle+ let body = if is_file repodir+ then sbundle+ else make_email repodir [] Nothing sbundle Nothing+ rval <- remote_apply opts repodir body+ let pull_reminder =+ if num_to_pull > 0+ then text $ "(By the way, the remote repository has " ++ show num_to_pull ++ " "+ ++ englishNum num_to_pull (Noun "patch") " to pull.)"+ else empty+ case rval of ExitFailure ec -> do putStrLn $ "Apply failed!"+ exitWith (ExitFailure ec)+ ExitSuccess -> putInfo $ text "Push successful." $$ pull_reminder++push_cmd _ _ = impossible++want_sign :: [DarcsFlag] -> Bool+want_sign opts = case opts of+ [] -> False+ Sign:_ -> True+ (SignAs _):_ -> True+ (SignSSL _):_ -> True+ NoSign:_ -> False+ _:opts' -> want_sign opts'+\end{code}++For obvious reasons, you can only push to repositories to which you have+write access. In addition, you can only push to repos that you access+either on the local file system or with ssh. In order to apply with ssh,+darcs must also be installed on the remote computer. The command invoked+to run ssh may be configured by the \verb!DARCS_SSH! environment variable+(see subsection~\ref{env:DARCS_SSH}). The command invoked via ssh is always+\verb!darcs!, i.e.\ the darcs executable must be in the default path on+the remote machine.++Push works by creating a patch bundle, and then running darcs apply in the+target repository using that patch bundle. This means that the default+options for \emph{apply} in the \emph{target} repository (such as, for+example, \verb!--test!) will affect the behavior of push. This also means+that push is somewhat less efficient than pull.++When you receive an error message such as+\begin{verbatim}+bash: darcs: command not found+\end{verbatim}+then this means that the darcs on the remote machine could+not be started. Make sure that the darcs executable is called+\verb!darcs! and is found in the default path. The default path can+be different in interactive and in non-interactive shells. Say+\begin{verbatim}+ssh login@remote.machine darcs+\end{verbatim}+to try whether the remote darcs can be found, or+\begin{verbatim}+ssh login@remote.machine 'echo $PATH'+\end{verbatim}+(note the single quotes) to check the default path.++\begin{options}+--apply-as+\end{options}++If you give the \verb!--apply-as! flag, darcs will use sudo to apply the+changes as a different user. This can be useful if you want to set up a+system where several users can modify the same repository, but you don't+want to allow them full write access. This isn't secure against skilled+malicious attackers, but at least can protect your repository from clumsy,+inept or lazy users.++\begin{options}+--matches, --patches, --tags, --no-deps+\end{options}++The \verb!--patches!, \verb!--matches!, \verb!--tags!, and \verb!--no-deps!+options can be used to select which patches to push, as described in+subsection~\ref{selecting}.++When there are conflicts, the behavior of push is determined by the default+flags to \verb!apply! in the \emph{target} repository. Most commonly, for+pushed-to repositories, you'd like to have \verb!--dont-allow-conflicts! as+a default option to apply (by default, it is already the default\ldots). If+this is the case, when there are conflicts on push, darcs will fail with an+error message. You can then resolve by pulling the conflicting patch,+recording a resolution and then pushing the resolution together with the+conflicting patch.++Darcs does not have an explicit way to tell you which patch conflicted, only the+file name. You may want to pull all the patches from the remote repository just+to be sure. If you don't want to do this in your working directory,+you can create another darcs working directory for this purpose.++If you want, you could set the target repository to use \verb!--allow-conflicts!.+In this case conflicting patches will be applied, but the conflicts will+not be marked in the working directory.++If, on the other hand, you have \verb!--mark-conflicts! specified as a+default flag for apply in the target repository, when there is a conflict,+it will be marked in the working directory of the target repository. In+this case, you should resolve the conflict in the target repository itself.
@@ -0,0 +1,137 @@+\darcsCommand{put}+\begin{code}+{-# OPTIONS_GHC -cpp #-}+{-# LANGUAGE CPP #-}++module Darcs.Commands.Put ( put ) where+import System.Exit ( ExitCode( ExitSuccess, ExitFailure ), exitWith )+import Control.Monad ( when )+import Data.Maybe ( catMaybes )+import System.Directory ( createDirectory )+import Darcs.Commands ( DarcsCommand(..), nodefaults )+import Darcs.Arguments ( DarcsFlag( Quiet, Verbose,+ UseFormat2, UseHashedInventory, UseOldFashionedInventory ),+ applyas, match_one_context, fixUrl,+ network_options, flagToString, get_inventory_choices,+ set_scripts_executable, working_repo_dir, set_default+ )+import Darcs.Repository ( withRepoReadLock, ($-), patchSetToPatches, read_repo, amInRepository )+import Darcs.Repository.Format ( identifyRepoFormat,+ RepoProperty ( Darcs2, HashedInventory ), format_has )+import Darcs.Patch.Bundle ( make_bundle2 )+import Darcs.Ordered ( FL(..) )+import Darcs.Match ( have_patchset_match, get_one_patchset )+import Darcs.Repository.Prefs ( get_preflist, set_defaultrepo )+import Darcs.URL ( is_url, is_file )+import Darcs.Utils ( withCurrentDirectory )+import Progress ( debugMessage )+import Darcs.RepoPath ( ioAbsoluteOrRemote, toPath )+import Darcs.SlurpDirectory ( empty_slurpy )+import Darcs.External ( execSSH )+import Darcs.RemoteApply ( remote_apply )+import Darcs.Commands.Init ( initialize )+import Darcs.Email ( make_email )+import Darcs.Sealed ( Sealed(..), seal )+#include "impossible.h"++put_description :: String +put_description =+ "Makes a copy of the repository"++put_help :: String+put_help =+ "The `darcs put' command creates a copy of the current repository. It\n" +++ "is currently very inefficient, so when creating local copies you\n" +++ "should use `darcs get . x' instead of `darcs put x'.\n" +++ "\n" +++ "Currently this command just uses `darcs init' to create the target\n" +++ "repository, then `darcs push --all' to copy patches to it. Options\n" +++ "passed to `darcs put' are passed to the init and/or push commands as\n" +++ "appropriate. See those commands for an explanation of each option.\n"++put ::DarcsCommand+put = DarcsCommand {command_name = "put",+ command_help = put_help,+ command_description = put_description,+ command_extra_args = 1,+ command_extra_arg_help = ["<NEW REPOSITORY>"],+ command_command = put_cmd,+ command_prereq = amInRepository,+ command_get_arg_possibilities = get_preflist "repos",+ command_argdefaults = nodefaults,+ command_advanced_options = [applyas] ++ network_options,+ command_basic_options = [match_one_context, set_scripts_executable,+ get_inventory_choices,+ set_default, working_repo_dir]}++put_cmd :: [DarcsFlag] -> [String] -> IO ()+put_cmd _ [""] = fail "Empty repository argument given to put."+put_cmd opts [unfixedrepodir] =+ let am_quiet = Quiet `elem` opts+ putInfo s = when (not am_quiet) $ putStrLn s+ putVerbose = when (Verbose `elem` opts) . putStrLn+ in+ do+ repodir <- fixUrl opts unfixedrepodir+ -- Test to make sure we aren't trying to push to the current repo+ t_cur_absolute_repo_dir <- ioAbsoluteOrRemote "."+ t_req_absolute_repo_dir <- ioAbsoluteOrRemote repodir+ let cur_absolute_repo_dir = toPath t_cur_absolute_repo_dir+ req_absolute_repo_dir = toPath t_req_absolute_repo_dir+ when (cur_absolute_repo_dir == req_absolute_repo_dir) $+ fail "Can't put to current repository!"+ when (is_url req_absolute_repo_dir) $ error "Can't put to a URL!"++ debugMessage "Creating repository"+ putVerbose "Creating repository"+ rf_or_e <- identifyRepoFormat "."+ rf <- case rf_or_e of Left e -> fail e+ Right x -> return x+ let initopts = if format_has Darcs2 rf+ then UseFormat2:filter (/= UseOldFashionedInventory) opts+ else if format_has HashedInventory rf &&+ not (UseOldFashionedInventory `elem` opts)+ then UseHashedInventory:filter (/= UseFormat2) opts+ else UseOldFashionedInventory:filter (/= UseFormat2) opts+ if is_file req_absolute_repo_dir+ then do createDirectory req_absolute_repo_dir+ withCurrentDirectory req_absolute_repo_dir $ (command_command initialize) initopts []+ else do -- is_ssh req_absolute_repo_dir+ remoteInit req_absolute_repo_dir initopts++ withCurrentDirectory cur_absolute_repo_dir $+ withRepoReadLock opts $- \repository -> do+ set_defaultrepo req_absolute_repo_dir opts+ Sealed patchset <- if have_patchset_match opts+ then get_one_patchset repository opts -- todo: make sure get_one_patchset has the right type+ else read_repo repository >>= (return . seal)+ Sealed patchset2 <- if have_patchset_match opts+ then get_one_patchset repository opts -- todo: make sure get_one_patchset has the right type+ else read_repo repository >>= (return . seal)+ let patches = patchSetToPatches patchset+ patches2 = patchSetToPatches patchset2+ nullFL NilFL = True+ nullFL _ = False+ when (nullFL patches) $ do+ putInfo "No patches were selected to put. Nothing to be done."+ exitWith ExitSuccess+ let bundle = (make_bundle2 opts empty_slurpy [] patches patches2)+ message = if is_file req_absolute_repo_dir+ then bundle+ else make_email req_absolute_repo_dir [] Nothing bundle Nothing+ putVerbose "Applying patches in new repository..."+ rval <- remote_apply opts req_absolute_repo_dir message+ case rval of ExitFailure ec -> do putStrLn $ "Apply failed!"+ exitWith (ExitFailure ec)+ ExitSuccess -> putInfo "Put successful."+put_cmd _ _ = impossible++remoteInit :: FilePath -> [DarcsFlag] -> IO ()+remoteInit repo opts = do+ let args = catMaybes $ map (flagToString $ command_basic_options initialize) opts+ command = "darcs initialize --repodir='" ++ path ++ "' " ++ unwords args+ exitCode <- execSSH addr command+ when (exitCode /= ExitSuccess) $ + fail "Couldn't initialize remote repository."+ where (addr,':':path) = break (==':') repo+\end{code}
@@ -0,0 +1,511 @@+% Copyright (C) 2002-2003 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\darcsCommand{record}+\begin{code}+{-# OPTIONS_GHC -cpp -fglasgow-exts #-}+{-# LANGUAGE CPP, PatternGuards #-}++module Darcs.Commands.Record ( record, commit, get_date, get_log, file_exists ) where+import Control.Exception ( handleJust, Exception( ExitException ) )+import Control.Monad ( filterM, when )+import System.IO ( hGetContents, stdin )+import Data.List ( sort, isPrefixOf )+import System.Exit ( exitWith, exitFailure, ExitCode(..) )+import System.IO ( hPutStrLn )+import System.Directory ( doesFileExist, doesDirectoryExist, removeFile )+import Data.Maybe ( isJust )++import Darcs.Lock ( readBinFile, writeBinFile, world_readable_temp, appendToFile, removeFileMayNotExist )+import Darcs.Hopefully ( info, n2pia )+import Darcs.Repository ( Repository, amInRepository, withRepoLock, ($-),+ get_unrecorded_in_files,+ get_unrecorded_in_files_unsorted, withGutsOf,+ sync_repo, read_repo,+ slurp_recorded,+ tentativelyAddPatch, finalizeRepositoryChanges,+ )+import Darcs.Patch ( RepoPatch, Patch, Prim, namepatch, summary, anonymous,+ adddeps, fromPrims )+import Darcs.Ordered ( FL(..), RL(..), (:>)(..), (+>+),+ unsafeUnFL, unsafeCompare,+ reverseRL, mapFL, mapFL_FL, nullFL )+import Darcs.Patch.Info ( PatchInfo )+import Darcs.SlurpDirectory ( slurp_hasfile, slurp_hasdir )+import Darcs.Patch.Choices ( patch_choices_tps, tp_patch,+ force_first, get_choices, tag )+import Darcs.SelectChanges ( with_selected_changes_to_files',+ with_selected_changes_reversed )+import Darcs.RepoPath ( FilePathLike, SubPath, sp2fn, toFilePath )+import Darcs.SlurpDirectory ( Slurpy, empty_slurpy )+import Darcs.Commands ( DarcsCommand(..), nodefaults, loggers, command_stub )+import Darcs.Arguments ( DarcsFlag( PromptLongComment, NoEditLongComment,+ EditLongComment, RmLogFile, LogFile, Pipe,+ PatchName, AskDeps, All ),+ get_author, working_repo_dir, lookforadds,+ fixSubPaths, defineChanges, testByDefault,+ ask_long_comment, askdeps, patch_select_flag,+ all_pipe_interactive, leave_test_dir, notest,+ author, patchname_option, umask_option, ignoretimes,+ nocompress, rmlogfile, logfile, list_registered_files,+ set_scripts_executable )+import Darcs.Utils ( askUser, promptYorn, edit_file, clarify_errors )+import Progress ( debugMessage)+import Darcs.ProgressPatches( progressFL)+import IsoDate ( getIsoDateTime, cleanLocalDate )+import Printer ( hPutDocLn, text, wrap_text, ($$), renderString )+import Darcs.Gorsvet( invalidateIndex )+#include "impossible.h"++record_description :: String+record_description =+ "Save changes in the working copy to the repository as a patch."+\end{code}++If you provide one or more files or directories as additional arguments+to record, you will only be prompted to changes in those files or+directories.+\begin{code}+record_help :: String+record_help = renderString $ wrap_text 80 $+ "Record is used to name a set of changes and record the patch to the "+++ "repository."++record :: DarcsCommand+record = DarcsCommand {command_name = "record",+ command_help = record_help,+ command_description = record_description,+ command_extra_args = -1,+ command_extra_arg_help = ["[FILE or DIRECTORY]..."],+ command_command = record_cmd,+ command_prereq = amInRepository,+ command_get_arg_possibilities = list_registered_files,+ command_argdefaults = nodefaults,+ command_advanced_options = [logfile, rmlogfile,+ nocompress, ignoretimes,+ umask_option,+ set_scripts_executable],+ command_basic_options = [patchname_option, author,+ notest,+ leave_test_dir,+ all_pipe_interactive,+ askdeps,+ ask_long_comment,+ lookforadds,+ working_repo_dir]}++commit_description :: String+commit_description =+ "Does not actually do anything, but offers advice on saving changes"++commit_help :: String+commit_help =+ "This command does not do anything.\n"+++ "If you want to save changes locally, use the `darcs record' command.\n"+++ "If you want to save a recorded patch to another repository, use the\n"+++ "`darcs push' or `darcs send' commands instead.\n"++commit :: DarcsCommand+commit = command_stub "commit" commit_help commit_description record++file_exists :: Slurpy -> SubPath -> IO Bool+file_exists s rp = do file <- doesFileExist fp+ dir <- doesDirectoryExist fp+ return (file || dir ||+ slurp_hasfile (sp2fn rp) s ||+ slurp_hasdir (sp2fn rp) s)+ where fp = toFilePath rp++record_cmd :: [DarcsFlag] -> [String] -> IO ()+record_cmd opts args = do+ check_name_is_not_option opts+ let (logMessage,_, _) = loggers opts+ withRepoLock (testByDefault opts) $- \repository -> do+ rec <- if null args then return empty_slurpy+ else slurp_recorded repository+ files <- sort `fmap` fixSubPaths opts args+ let non_repo_files = if null files && (not $ null args) then args else []+ existing_files <- filterM (file_exists rec) files+ non_existent_files <- filterM (fmap not . file_exists rec) files+ when (not $ null existing_files) $+ logMessage $ "Recording changes in "++unwords (map show existing_files)++":\n"+ when (not $ null non_existent_files) $+ logMessage $ "Non existent files or directories: "++unwords (map show non_existent_files)++"\n"+ when (((not $ null non_existent_files) || (not $ null non_repo_files)) && null existing_files) $+ fail "None of the files you specified exist!"+ debugMessage "About to get the unrecorded changes."+ let existing_fns = map sp2fn existing_files+ changes <- if All `elem` opts then get_unrecorded_in_files_unsorted repository existing_fns+ else get_unrecorded_in_files repository existing_fns+ debugMessage "I've gotten unrecorded."+ case allow_empty_with_askdeps changes of+ Nothing -> do when (Pipe `elem` opts) $ do get_date opts+ return ()+ if ((not $ null existing_files) || (not $ null non_existent_files))+ then logMessage "No changes in selected files or directories!"+ else logMessage "No changes!"+ Just ch -> do_record repository opts existing_files ch+ where allow_empty_with_askdeps NilFL+ | AskDeps `elem` opts = Just NilFL+ | otherwise = Nothing+ allow_empty_with_askdeps p = Just p++ -- check that what we treat as the patch name is not accidentally a command+ -- line flag+check_name_is_not_option :: [DarcsFlag] -> IO ()+check_name_is_not_option opts = do+ let (logMessage, _, _) = loggers opts+ patchNames = [n | PatchName n <- opts]+ when (length patchNames == 1) $ do+ let n = head patchNames+ oneLetterName = length n == 1 || (length n == 2 && head n == '-')+ if (oneLetterName && not (elem All opts))+ then do+ let keepAsking = do+ yorn <- promptYorn ("You specified " ++ show n ++ " as the patch name. Is that really what you want?")+ case yorn of + 'y' -> return ()+ 'n' -> do+ logMessage "Okay, aborting the record."+ exitFailure+ _ -> keepAsking+ keepAsking+ else return ()+++do_record :: RepoPatch p => Repository p -> [DarcsFlag] -> [SubPath] -> FL Prim -> IO ()+do_record repository opts files ps = do+ let make_log = world_readable_temp "darcs-record"+ date <- get_date opts+ my_author <- get_author opts+ debugMessage "I'm slurping the repository."+ debugMessage "About to select changes..."+ with_selected_changes_to_files' "record" opts+ (map toFilePath files) ps $ \ (chs:>_) ->+ do when (is_empty_but_not_askdeps chs) $+ do putStrLn "Ok, if you don't want to record anything, that's fine!"+ exitWith ExitSuccess+ handleJust only_successful_exits (\_ -> return ()) $+ do deps <- if AskDeps `elem` opts+ then ask_about_depends repository chs opts+ else return []+ when (AskDeps `elem` opts) $ debugMessage "I've asked about dependencies."+ if nullFL chs && null deps+ then putStrLn "Ok, if you don't want to record anything, that's fine!"+ else do defineChanges chs+ (name, my_log, logf) <- get_log opts Nothing make_log chs+ do_actual_record repository opts name date+ my_author my_log logf deps chs+ where is_empty_but_not_askdeps l+ | AskDeps `elem` opts = False+ -- a "partial tag" patch; see below.+ | otherwise = nullFL l++do_actual_record :: RepoPatch p => Repository p -> [DarcsFlag] -> String -> String -> String+ -> [String] -> Maybe String+ -> [PatchInfo] -> FL Prim -> IO ()+do_actual_record repository opts name date my_author my_log logf deps chs =+ do debugMessage "Writing the patch file..."+ mypatch <- namepatch date name my_author my_log $+ fromPrims $ progressFL "Writing changes:" chs+ tentativelyAddPatch repository opts $ n2pia $ adddeps mypatch deps+ invalidateIndex repository+ debugMessage "Applying to pristine..."+ withGutsOf repository (finalizeRepositoryChanges repository)+ `clarify_errors` failuremessage+ debugMessage "Syncing timestamps..."+ sync_repo repository+ when (isJust logf) $ removeFile (fromJust logf)+ logMessage $ "Finished recording patch '"++name++"'"+ where (logMessage,_,_) = loggers opts+ failuremessage = "Failed to record patch '"++name++"'" +++ case logf of Just lf -> "\nLogfile left in "++lf++"."+ Nothing -> ""+\end{code}+Each patch is given a name, which typically would consist of a brief+description of the changes. This name is later used to describe the patch.+The name must fit on one line (i.e.\ cannot have any embedded newlines). If+you have more to say, stick it in the log.+\begin{code}+\end{code}++The patch is also flagged with the author of the change, taken by default+from the \verb!DARCS_EMAIL! environment variable, and if that doesn't+exist, from the \verb!EMAIL! environment variable. The date on which the+patch was recorded is also included. Currently there is no provision for+keeping track of when a patch enters a given repository.+\begin{code}+get_date :: [DarcsFlag] -> IO String+get_date opts+ | Pipe `elem` opts = do cleanLocalDate `fmap` askUser "What is the date? "+get_date _ = getIsoDateTime+\end{code}+\label{DARCS_EDITOR}+Finally, each changeset should have a full log (which may be empty). This+log is for detailed notes which are too lengthy to fit in the name. If you+answer that you do want to create a comment file, darcs will open an editor+so that you can enter the comment in. The choice of editor proceeds as+follows. If one of the \verb!$DARCS_EDITOR!, \verb!$VISUAL! or+\verb!$EDITOR! environment variables is defined, its value is used (with+precedence proceeding in the order listed). If not, ``vi'', ``emacs'',+``emacs~-nw'' and ``nano'' are tried in that order.++\begin{options}+--logfile+\end{options}++If you wish, you may specify the patch name and log using the+\verb!--logfile! flag. If you do so, the first line of the specified file+will be taken to be the patch name, and the remainder will be the ``long+comment''. This feature can be especially handy if you have a test that+fails several times on the record (thus aborting the record), so you don't+have to type in the long comment multiple times. The file's contents will+override the \verb!--patch-name! option.++\begin{code}+data PName = FlagPatchName String | PriorPatchName String | NoPatchName++get_log :: [DarcsFlag] -> Maybe (String, [String]) -> IO String -> FL Prim ->+ IO (String, [String], Maybe String)+get_log opts m_old make_log chs = gl opts+ where patchname_specified = patchname_helper opts+ patchname_helper (PatchName n:_) | take 4 n == "TAG " = FlagPatchName $ '.':n+ | otherwise = FlagPatchName n+ patchname_helper (_:fs) = patchname_helper fs+ patchname_helper [] = case m_old of Just (p,_) -> PriorPatchName p+ Nothing -> NoPatchName+ default_log = case m_old of+ Nothing -> []+ Just (_,l) -> l+ gl (Pipe:_) = do p <- case patchname_specified of+ FlagPatchName p -> return p+ PriorPatchName p -> return p+ NoPatchName -> prompt_patchname False+ putStrLn "What is the log?"+ thelog <- lines `fmap` hGetContents stdin -- ratify hGetContents: stdin not deleted+ return (p, thelog, Nothing)+ gl (LogFile f:fs) =+ do -- round 1 (patchname)+ mlp <- lines `fmap` readBinFile f `catch` (\_ -> return [])+ firstname <- case (patchname_specified, mlp) of+ (FlagPatchName p, []) -> return p+ (_, p:_) -> return p -- logfile trumps prior!+ (PriorPatchName p, []) -> return p+ (NoPatchName, []) -> prompt_patchname True+ -- round 2+ append_info f firstname+ when (EditLongComment `elem` fs) $ do edit_file f+ return ()+ (name, thelog, _) <- read_long_comment f firstname+ when (RmLogFile `elem` opts) $ removeFileMayNotExist f+ return (name, thelog, Nothing)+ gl (EditLongComment:_) =+ case patchname_specified of+ FlagPatchName p -> actually_get_log p+ PriorPatchName p -> actually_get_log p+ NoPatchName -> prompt_patchname True >>= actually_get_log+ gl (NoEditLongComment:_) =+ case patchname_specified of+ FlagPatchName p+ | Just ("",_) <- m_old ->+ return (p, default_log, Nothing) -- rollback -m+ FlagPatchName p -> return (p, default_log, Nothing) -- record (or amend) -m+ PriorPatchName p -> return (p, default_log, Nothing) -- amend+ NoPatchName -> do p <- prompt_patchname True -- record+ return (p, [], Nothing)+ gl (PromptLongComment:fs) =+ case patchname_specified of+ FlagPatchName p -> prompt_long_comment p -- record (or amend) -m+ _ -> gl fs+ gl (_:fs) = gl fs+ gl [] = case patchname_specified of+ FlagPatchName p -> return (p, default_log, Nothing) -- record (or amend) -m+ PriorPatchName "" -> prompt_patchname True >>= prompt_long_comment+ PriorPatchName p -> return (p, default_log, Nothing)+ NoPatchName -> prompt_patchname True >>= prompt_long_comment+ prompt_patchname retry =+ do n <- askUser "What is the patch name? "+ if n == "" || take 4 n == "TAG "+ then if retry then prompt_patchname retry+ else fail "Bad patch name!"+ else return n+ prompt_long_comment oldname =+ do yorn <- promptYorn "Do you want to add a long comment?"+ if yorn == 'y' then actually_get_log oldname+ else return (oldname, [], Nothing)+ actually_get_log p = do logf <- make_log+ writeBinFile logf $ unlines $ p : default_log+ append_info logf p+ edit_file logf+ read_long_comment logf p+ read_long_comment :: FilePathLike p => p -> String -> IO (String, [String], Maybe p)+ read_long_comment f oldname =+ do t <- (lines.filter (/='\r')) `fmap` readBinFile f+ case t of [] -> return (oldname, [], Just f)+ (n:ls) -> return (n, takeWhile+ (not.(eod `isPrefixOf`)) ls,+ Just f)+ append_info f oldname =+ do fc <- readBinFile f+ appendToFile f $ \h ->+ do case fc of+ _ | null (lines fc) -> hPutStrLn h oldname+ | last fc /= '\n' -> hPutStrLn h ""+ | otherwise -> return ()+ hPutDocLn h $ text eod+ $$ text ""+ $$ wrap_text 75+ ("Place the long patch description above the "+++ eod+++ " marker. The first line of this file "+++ "will be the patch name.")+ $$ text ""+ $$ text "This patch contains the following changes:"+ $$ text ""+ $$ summary (fromPrims chs :: Patch)++eod :: String+eod = "***END OF DESCRIPTION***"+\end{code}++\begin{options}+--ask-deps+\end{options}++Each patch may depend on any number of previous patches. If you choose to+make your patch depend on a previous patch, that patch is required to be+applied before your patch can be applied to a repository. This can be used, for+example, if a piece of code requires a function to be defined, which was+defined in an earlier patch.++If you want to manually define any dependencies for your patch, you can use+the \verb!--ask-deps! flag, and darcs will ask you for the patch's+dependencies.++It is possible to record a patch which has no actual changes but which+has specific dependencies. This type of patch can be thought of as a+``partial tag''. The \verb!darcs tag! command will record a patch+with no actual changes but which depends on the entire current+inventory of the repository. The \verb!darcs record --ask-deps! with+no selected changes will record a patch that depends on only those+patches selected via the \verb!--ask-deps! operation, resulting in a+patch which describes a set of patches; the presence of this primary+patch in a repository implies the presence of (at least) the+depended-upon patches.++\begin{code}+ask_about_depends :: RepoPatch p => Repository p -> FL Prim -> [DarcsFlag] -> IO [PatchInfo]+ask_about_depends repository pa' opts = do+ pps <- read_repo repository+ pa <- n2pia `fmap` anonymous (fromPrims pa')+ let ps = (reverseRL $ headRL pps)+>+(pa:>:NilFL)+ (pc, tps) = patch_choices_tps ps+ ta = case filter ((pa `unsafeCompare`) . tp_patch) $ unsafeUnFL tps of+ [tp] -> tag tp+ [] -> error "ask_about_depends: []"+ _ -> error "ask_about_depends: many"+ ps' = mapFL_FL tp_patch $ middle_choice $ force_first ta pc+ with_selected_changes_reversed "depend on" (filter askdep_allowed opts) ps'+ $ \(deps:>_) -> return $ mapFL info deps+ where headRL (x:<:_) = x+ headRL NilRL = impossible+ askdep_allowed = not . patch_select_flag+ middle_choice p = mc where (_ :> mc :> _) = get_choices p+++only_successful_exits :: Exception -> Maybe ()+only_successful_exits (ExitException ExitSuccess) = Just ()+only_successful_exits _ = Nothing+\end{code}++\begin{options}+--no-test, --test+\end{options}++If you configure darcs to run a test suite, darcs will run this test on the+recorded repository to make sure it is valid. Darcs first creates a pristine+copy of the source tree (in a temporary directory), then it runs the test,+using its return value to decide if the record is valid. If it is not valid,+the record will be aborted. This is a handy way to avoid making stupid+mistakes like forgetting to `darcs add' a new file. It also can be+tediously slow, so there is an option (\verb!--no-test!) to skip the test.++\begin{options}+--set-scripts-executable+\end{options}++If you pass \verb!--set-scripts-executable! to \verb!darcs record!, darcs will set scripts+executable in the test directory before running the test.++\begin{options}+--pipe+\end{options}++If you run record with the \verb!--pipe! option, you will be prompted for+the patch date, author, and the long comment. The long comment will extend+until the end of file or stdin is reached (ctrl-D on Unixy systems, ctrl-Z+on systems running a Microsoft OS).++This interface is intended for scripting darcs, in particular for writing+repository conversion scripts. The prompts are intended mostly as a useful+guide (since scripts won't need them), to help you understand the format in+which to provide the input. Here's an example of what the \verb!--pipe!+prompts look like:++\begin{verbatim}+ What is the date? Mon Nov 15 13:38:01 EST 2004+ Who is the author? David Roundy+ What is the log? One or more comment lines+\end{verbatim}+++\begin{options}+--interactive+\end{options}++By default, \verb!record! works interactively. Probably the only thing you need+to know about using this is that you can press \verb!?! at the prompt to be+shown a list of the rest of the options and what they do. The rest should be+clear from there. Here's a+``screenshot'' to demonstrate:++\begin{verbatim}+hunk ./hello.pl +2++#!/usr/bin/perl++print "Hello World!\n";+Shall I record this patch? (2/2) [ynWsfqadjk], or ? for help: ?+How to use record...+y: record this patch+n: don't record it+w: wait and decide later, defaulting to no++s: don't record the rest of the changes to this file+f: record the rest of the changes to this file++d: record selected patches+a: record all the remaining patches+q: cancel record++j: skip to next patch+k: back up to previous patch+h or ?: show this help++<Space>: accept the current default (which is capitalized)++\end{verbatim}+What you can't see in that ``screenshot'' is that \verb!darcs! will also try to use+color in your terminal to make the output even easier to read.
@@ -0,0 +1,131 @@+% Copyright (C) 2002-2004 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\darcsCommand{remove}+\begin{code}+{-# OPTIONS_GHC -cpp #-}+{-# LANGUAGE CPP #-}++module Darcs.Commands.Remove ( remove, rm, unadd ) where++import Control.Monad ( when )+import Darcs.Commands ( DarcsCommand(..), nodefaults,+ command_alias, command_stub,+ )+import Darcs.Arguments ( DarcsFlag, fixSubPaths,+ list_registered_files,+ working_repo_dir, umask_option+ )+import Darcs.RepoPath ( SubPath, toFilePath, sp2fn )+import Darcs.Repository ( Repository, withRepoLock, ($-), amInRepository,+ slurp_pending, slurp_recorded,+ get_unrecorded_in_files, add_to_pending )+import Darcs.Patch ( RepoPatch, Prim, apply_to_slurpy, adddir, rmdir, addfile, rmfile )+import Darcs.Ordered ( FL(..), (+>+) )+import Darcs.SlurpDirectory ( slurp_removedir, slurp_removefile )+import Darcs.Repository.Prefs ( filetype_function )+import Darcs.Diff ( unsafeDiff )+import Darcs.Gorsvet( invalidateIndex )+#include "impossible.h"++remove_description :: String+remove_description = "Remove files from version control."++remove_help :: String+remove_help =+ "The `darcs remove' command exists primarily for symmetry with `darcs\n" +++ "add', as the normal way to remove a file from version control is\n" +++ "simply to delete it from the working tree. This command is only\n" +++ "useful in the unusual case where one wants to record a removal patch\n" +++ "WITHOUT deleting the copy in the working tree (which can be re-added).\n" +++ "\n" +++ "Note that applying a removal patch to a repository (e.g. by pulling\n" +++ "the patch) will ALWAYS affect the working tree of that repository.\n"++remove :: DarcsCommand+remove = DarcsCommand {command_name = "remove",+ command_help = remove_help,+ command_description = remove_description,+ command_extra_args = -1,+ command_extra_arg_help = ["<FILE or DIRECTORY> ..."],+ command_command = remove_cmd,+ command_prereq = amInRepository,+ command_get_arg_possibilities = list_registered_files,+ command_argdefaults = nodefaults,+ command_advanced_options = [umask_option],+ command_basic_options =+ [working_repo_dir]}++remove_cmd :: [DarcsFlag] -> [String] -> IO ()+remove_cmd opts relargs =+ withRepoLock opts $- \repository -> do+ args <- fixSubPaths opts relargs+ when (null args) $+ putStrLn "Nothing specified, nothing removed."+ p <- make_remove_patch repository args+ invalidateIndex repository+ add_to_pending repository p++make_remove_patch :: RepoPatch p => Repository p -> [SubPath] -> IO (FL Prim)+make_remove_patch repository files =+ do s <- slurp_pending repository+ srecorded <- slurp_recorded repository+ pend <- get_unrecorded_in_files repository (map sp2fn files)+ let sunrec = fromJust $ apply_to_slurpy pend srecorded+ wt <- filetype_function+ mrp wt s sunrec files+ where mrp wt s sunrec (f:fs) =+ case slurp_removedir fn s of+ Just s' ->+ case slurp_removedir fn sunrec of+ Just sunrec' -> do rest <- mrp wt s' sunrec' fs+ return $ rmdir f_fp :>: rest+ Nothing -> do rest <- mrp wt s' sunrec fs+ return $ adddir f_fp :>: rmdir f_fp :>: rest+ Nothing ->+ case slurp_removefile fn s of+ Nothing -> fail $ "Can't remove "++f_fp+ Just s' ->+ case slurp_removefile fn sunrec of+ Nothing -> do rest <- mrp wt s' sunrec fs+ return $ addfile f_fp :>: rmfile f_fp :>: rest+ Just sunrec' -> do rest <- mrp wt s' sunrec' fs+ let newp = unsafeDiff [] wt sunrec sunrec'+ return $ newp +>+ rest+ where fn = sp2fn f+ f_fp = toFilePath f+ mrp _ _ _ [] = return NilFL++rm_description :: String+rm_description = "Help newbies find `darcs remove'."++rm_help :: String+rm_help =+ "The `darcs rm' command does nothing.\n" +++ "\n" +++ "The normal way to remove a file from version control is simply to\n" +++ "delete it from the working tree. To remove a file from version\n" +++ "control WITHOUT affecting the working tree, see `darcs remove'.\n"++rm :: DarcsCommand+rm = command_stub "rm" rm_help rm_description remove++unadd :: DarcsCommand+unadd = command_alias "unadd" remove+\end{code}++
@@ -0,0 +1,70 @@+% Copyright (C) 2003,2005 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\darcsCommand{repair}+\begin{code}+module Darcs.Commands.Repair ( repair ) where+import System.IO++import Darcs.Commands+import Darcs.Arguments ( DarcsFlag(),+ working_repo_dir, umask_option,+ )+import Darcs.Repository ( withRepoLock, ($-), amInRepository,+ replacePristineFromSlurpy, writePatchSet )+import Darcs.Repository.Repair( replayRepository,+ RepositoryConsistency(..) )++repair_description :: String+repair_description = "Repair a corrupted repository."++repair_help :: String+repair_help =+ "The `darcs repair' command attempts to fix corruption in the current\n" +++ "repository. Currently it can only repair damage to the pristine tree,\n" +++ "which is where most corruption occurs.\n"++repair :: DarcsCommand+repair = DarcsCommand {command_name = "repair",+ command_help = repair_help,+ command_description = repair_description,+ command_extra_args = 0,+ command_extra_arg_help = [],+ command_command = repair_cmd,+ command_prereq = amInRepository,+ command_get_arg_possibilities = return [],+ command_argdefaults = nodefaults,+ command_advanced_options = [umask_option],+ command_basic_options = [working_repo_dir]}++repair_cmd :: [DarcsFlag] -> [String] -> IO ()+repair_cmd opts _ = withRepoLock opts $- \repository -> do+ replayRepository repository opts $ \state ->+ case state of+ RepositoryConsistent ->+ putStrLn "The repository is already consistent, no changes made."+ BrokenPristine s -> do+ putStrLn "Fixing pristine tree..."+ replacePristineFromSlurpy repository s+ BrokenPatches s newps -> do+ putStrLn "Writing out repaired patches..."+ writePatchSet newps opts+ putStrLn "Fixing pristine tree..."+ replacePristineFromSlurpy repository s+ return ()++\end{code}
@@ -0,0 +1,232 @@+% Copyright (C) 2002-2005 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\darcsCommand{replace}+\begin{code}+{-# OPTIONS_GHC -cpp #-}+{-# LANGUAGE CPP #-}++module Darcs.Commands.Replace ( replace ) where++import Data.Maybe ( isJust )+import Control.Monad ( unless )++import Darcs.Commands ( DarcsCommand(DarcsCommand, command_name, command_help,+ command_description, command_extra_args,+ command_extra_arg_help, command_command, command_prereq,+ command_get_arg_possibilities, command_argdefaults,+ command_advanced_options, command_basic_options),+ nodefaults )+import Darcs.Arguments ( DarcsFlag(ForceReplace, Toks), list_registered_files,+ ignoretimes, umask_option, tokens, force_replace,+ working_repo_dir, fixSubPaths )+import Darcs.Repository ( withRepoLock, ($-),+ add_to_pending, slurp_pending,+ amInRepository, slurp_recorded_and_unrecorded,+ applyToWorking,+ )+import Darcs.Patch ( Prim, apply_to_slurpy, tokreplace, force_replace_slurpy )+import Darcs.Ordered ( FL(..), unsafeFL, (+>+), concatFL )+import Darcs.SlurpDirectory ( slurp_hasfile, Slurpy )+import RegChars ( regChars )+import Data.Char ( isSpace )+import Darcs.Diff ( unsafeDiff )+import Darcs.RepoPath ( SubPath, sp2fn, toFilePath )+import Darcs.Repository.Prefs ( FileType(TextFile) )+#include "impossible.h"++replace_description :: String+replace_description = "Substitute one word for another."++replace_help :: String+replace_help =+ "In addition to line-based patches, Darcs supports a limited form of\n" +++ "lexical substitution. Files are treated as sequences of words, and\n" +++ "each occurrence of the old word is replaced by the new word.\n" +++ "This is intended to provide a clean way to rename a function or\n" +++ "variable. Such renamings typically affect lines all through the\n" +++ "source code, so a traditional line-based patch would be very likely to\n" +++ "conflict with other branches, requiring manual merging.\n" +++ "\n" +++ "Files are tokenized according to one simple rule: words are strings of\n" +++ "valid token characters, and everything between them (punctuation and\n" +++ "whitespace) is discarded.\n" +++ "\n" +++ "The tokenizer treats files as byte strings, so it is not possible for\n" +++ "--token-chars to include multi-byte characters, such as the non-ASCII\n" +++ "parts of UTF-8. Similarly, trying to replace a `high-bit' character\n" +++ "from a unibyte encoding will also result in replacement of the same\n" +++ "byte in files with different encodings. For example, an acute a from\n" +++ "ISO 8859-1 will also match an alpha from ISO 8859-7.\n" +++ "\n" +++ -- FIXME: this heuristic is ham-fisted and silly. Can we drop it?+ "By default, valid token characters are letters, numbers and the\n" +++ "underscore (i.e. [A-Za-z0-9_]). However if the old and/or new token\n" +++ "contains either a hyphen or period, BOTH hyphen and period are treated\n" +++ "as valid by default (i.e. [A-Za-z0-9_.-]).\n" +++ "\n" +++ "The set of valid characters can be customized using the --token-chars\n" +++ "option. The argument must be surrounded by square brackets. If a\n" +++ "hyphen occurs between two characters in the set, it is treated as a\n" +++ "set range. For example, in most locales [A-Z] denotes all uppercase\n" +++ "letters. If the first character is a caret, valid tokens are taken to\n" +++ "be the complement of the remaining characters. For example, [^ \\n\\t]\n" +++ "declares all characters except the space, tab and newline as valid\n" +++ "within a word. Unlike the tr(1) and grep(1) utilities, character\n" +++ "classes (such as [[:alnum:]]) are NOT supported.\n" +++ "\n" +++ "If you choose to use --token-chars, you are STRONGLY encouraged to do\n" +++ "so consistently. The consequences of using multiple replace patches\n" +++ "with different --token-chars arguments on the same file are not well\n" +++ "tested nor well understood.\n" +++ "\n" +++ "By default Darcs will refuse to perform a replacement if the new token\n" +++ "is already in use, because the replacements would be not be\n" +++ "distinguishable from the existing tokens. This behaviour can be\n" +++ "overridden by supplying the --force option, but an attempt to `darcs\n" +++ "rollback' the resulting patch will affect these existing tokens.\n"++-- FIXME: can we just delete the remaining text? It seems more an+-- instance of "look how clever I am; I made commutation work" rather+-- than information that is actually useful to users.+\end{code}+There is a potentially confusing difference, however, when a replace is+used to make another replace possible:+\begin{verbatim}+$ darcs replace newtoken aaack ./foo.c+$ darcs replace oldtoken newtoken ./foo.c+$ darcs record+\end{verbatim}+will be valid, even if \verb!newtoken! and \verb!oldtoken! are both present+in the recorded version of foo.c, while the sequence+\begin{verbatim}+$ [manually edit foo.c replacing newtoken with aaack]+$ darcs replace oldtoken newtoken ./foo.c+\end{verbatim}+will fail because ``newtoken'' still exists in the recorded version of+\verb!foo.c!. The reason for the difference is that when recording, a+``replace'' patch always is recorded \emph{before} any manual changes,+which is usually what you want, since often you will introduce new+occurrences of the ``newtoken'' in your manual changes. In contrast,+multiple ``replace'' changes are recorded in the order in which+they were made.+\begin{code}++replace :: DarcsCommand+replace = DarcsCommand {command_name = "replace",+ command_help = replace_help,+ command_description = replace_description,+ command_extra_args = -1,+ command_extra_arg_help = ["<OLD>","<NEW>",+ "<FILE> ..."],+ command_command = replace_cmd,+ command_prereq = amInRepository,+ command_get_arg_possibilities = list_registered_files,+ command_argdefaults = nodefaults,+ command_advanced_options = [ignoretimes, umask_option],+ command_basic_options =+ [tokens, force_replace, working_repo_dir]}++replace_cmd :: [DarcsFlag] -> [String] -> IO ()+replace_cmd opts (old:new:relfs) = withRepoLock opts $- \repository -> do+ fs <- fixSubPaths opts relfs+ toks <- choose_toks opts old new+ let checkToken tok =+ unless (is_tok toks tok) $ fail $ "'"++tok++"' is not a valid token!"+ checkToken old+ checkToken new+ (_, work) <- slurp_recorded_and_unrecorded repository+ cur <- slurp_pending repository+ pswork <- (concatFL . unsafeFL) `fmap` sequence (map (repl toks cur work) fs)+ add_to_pending repository pswork+ applyToWorking repository opts pswork `catch` \e ->+ fail $ "Can't do replace on working!\n"+ ++ "Perhaps one of the files already contains '"++ new++"'?\n"+ ++ show e+ where ftf _ = TextFile++ repl :: String -> Slurpy -> Slurpy -> SubPath -> IO (FL Prim)+ repl toks cur work f =+ if not $ slurp_hasfile (sp2fn f) work+ then do putStrLn $ "Skipping file '"++f_fp++"' which isn't in the repository."+ return NilFL+ else if ForceReplace `elem` opts ||+ isJust (apply_to_slurpy (tokreplace f_fp toks old new) work) ||+ isJust (apply_to_slurpy (tokreplace f_fp toks old new) cur)+ then return (get_force_replace f toks work)+ else do putStrLn $ "Skipping file '"++f_fp++"'"+ putStrLn $ "Perhaps the recorded version of this " +++ "file already contains '" ++new++"'?"+ putStrLn $ "Use the --force option to override."+ return NilFL+ where f_fp = toFilePath f++ get_force_replace :: SubPath -> String -> Slurpy -> FL Prim+ get_force_replace f toks s =+ case force_replace_slurpy (tokreplace f_fp toks new old) s of+ Nothing -> bug "weird forcing bug in replace."+ Just s' -> case unsafeDiff [] ftf s s' of+ pfix -> pfix +>+ (tokreplace f_fp toks old new :>: NilFL)+ where f_fp = toFilePath f++replace_cmd _ _ = fail "Usage: darcs replace OLD NEW [FILES]"++default_toks :: String+default_toks = "A-Za-z_0-9"+filename_toks :: String+filename_toks = "A-Za-z_0-9\\-\\."++-- | Given a set of characters and a string, returns true iff the+-- string contains only characters from the set. A set beginning with+-- a caret (@^@) is treated as a complementary set.+is_tok :: String -> String -> Bool+is_tok _ "" = False+is_tok toks s = and $ map (regChars toks) s++-- | This function checks for @--token-chars@ on the command-line. If+-- found, it validates the argument and returns it, without the+-- surrounding square brackets. Otherwise, it returns either+-- 'default_toks' or 'filename_toks' as explained in 'replace_help'.+-- +-- Note: Limitations in the current replace patch file format prevents+-- tokens and token-char specifiers from containing any whitespace.+choose_toks :: [DarcsFlag] -> String -> String -> IO String+choose_toks (Toks t:_) a b+ | length t <= 2 =+ bad_token_spec $ "It must contain more than 2 characters, because " +++ "it should be enclosed in square brackets"+ | head t /= '[' || last t /= ']' =+ bad_token_spec "It should be enclosed in square brackets"+ | '^' == head tok && length tok == 1 =+ bad_token_spec "Must be at least one character in the complementary set"+ | any isSpace t =+ bad_token_spec "Space is not allowed in the spec"+ | any isSpace a = bad_token_spec $ spacey_token a+ | any isSpace b = bad_token_spec $ spacey_token b+ | not (is_tok tok a) = bad_token_spec $ not_a_token a+ | not (is_tok tok b) = bad_token_spec $ not_a_token b+ | otherwise = return tok+ where tok = init $ tail t :: String+ bad_token_spec msg = fail $ "Bad token spec: '"++ t ++"' ("++ msg ++")"+ spacey_token x = x ++ " must not contain any space"+ not_a_token x = x ++ " is not a token, according to your spec"+choose_toks (_:fs) a b = choose_toks fs a b+choose_toks [] a b = if is_tok default_toks a && is_tok default_toks b+ then return default_toks+ else return filename_toks+\end{code}+
@@ -0,0 +1,119 @@+% Copyright (C) 2002-2005 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\darcsCommand{revert}+\begin{code}+module Darcs.Commands.Revert ( revert ) where+import System.Exit ( ExitCode(..), exitWith )+import Control.Monad ( when )+import Data.List ( sort )++import English (englishNum, This(..), Noun(..))+import Darcs.Commands ( DarcsCommand(..), nodefaults )+import Darcs.Arguments ( DarcsFlag( All, Debug ),+ ignoretimes, working_repo_dir,+ all_interactive,+ fixSubPaths, areFileArgs,+ list_registered_files, umask_option,+ )+import Darcs.Utils ( askUser )+import Darcs.RepoPath ( toFilePath, sp2fn )+import Darcs.Repository ( withRepoLock, ($-), withGutsOf,+ get_unrecorded_in_files,+ get_unrecorded_in_files_unsorted,+ add_to_pending, sync_repo,+ applyToWorking,+ amInRepository, slurp_recorded,+ )+import Darcs.Patch ( invert, apply_to_filepaths, commute )+import Darcs.Ordered ( FL(..), (:>)(..), lengthFL, nullFL, (+>+) )+import Darcs.SelectChanges ( with_selected_last_changes_to_files' )+import Darcs.Patch.TouchesFiles ( choose_touching )+import Darcs.Commands.Unrevert ( write_unrevert )+import Darcs.Sealed ( unsafeUnseal )+import Darcs.Gorsvet( invalidateIndex )++revert_description :: String+revert_description = "Discard unrecorded changes."++revert_help :: String+revert_help =+ "The `darcs revert' command discards unrecorded changes the working\n" +++ "tree. As with `darcs record', you will be asked which hunks (changes)\n" +++ "to revert. The --all switch can be used to avoid such prompting. If\n" +++ "files or directories are specified, other parts of the working tree\n" +++ "are not reverted.\n" +++ "\n" +++ "In you accidentally reverted something you wanted to keep (for\n" +++ "example, typing `darcs rev -a' instead of `darcs rec -a'), you can\n" +++ "immediately run `darcs unrevert' to restore it. This is only\n" +++ "guaranteed to work if the repository has not changed since `darcs\n" +++ "revert' ran.\n"++revert :: DarcsCommand+revert = DarcsCommand {command_name = "revert",+ command_help = revert_help,+ command_description = revert_description,+ command_extra_args = -1,+ command_extra_arg_help = ["[FILE or DIRECTORY]..."],+ command_command = revert_cmd,+ command_prereq = amInRepository,+ command_get_arg_possibilities = list_registered_files,+ command_argdefaults = nodefaults,+ command_advanced_options = [ignoretimes, umask_option],+ command_basic_options = [all_interactive,+ working_repo_dir]}++revert_cmd :: [DarcsFlag] -> [String] -> IO ()+revert_cmd opts args = withRepoLock opts $- \repository -> do+ files <- sort `fmap` fixSubPaths opts args+ let files_fn = map sp2fn files+ when (areFileArgs files) $+ putStrLn $ "Reverting changes in "++unwords (map show files)++"..\n"+ changes <- if All `elem` opts+ then get_unrecorded_in_files_unsorted repository files_fn+ else get_unrecorded_in_files repository files_fn+ let pre_changed_files = apply_to_filepaths (invert changes) (map toFilePath files)+ rec <- slurp_recorded repository+ case unsafeUnseal $ choose_touching pre_changed_files changes of+ NilFL -> putStrLn "There are no changes to revert!"+ _ -> with_selected_last_changes_to_files' "revert" opts+ pre_changed_files changes $ \ (norevert:>p) ->+ if nullFL p+ then putStrLn $ "If you don't want to revert after all," +++ " that's fine with me!"+ else do+ let theseChanges = englishNum (lengthFL p) . This . Noun $ "change"+ yorn <- if All `elem` opts+ then return "y"+ else askUser $ "Do you really want to revert " ++ theseChanges "? "+ case yorn of ('y':_) -> return ()+ _ -> exitWith $ ExitSuccess+ withGutsOf repository $ do+ invalidateIndex repository+ add_to_pending repository $ invert p+ when (Debug `elem` opts) $ putStrLn "About to write the unrevert file."+ case commute (norevert:>p) of+ Just (p':>_) -> write_unrevert repository p' rec NilFL+ Nothing -> write_unrevert repository (norevert+>+p) rec NilFL+ when (Debug `elem` opts) $ putStrLn "About to apply to the working directory."+ applyToWorking repository opts (invert p) `catch` \e ->+ fail ("Unable to apply inverse patch!" ++ show e)+ sync_repo repository+ putStrLn "Finished reverting."+\end{code}+
@@ -0,0 +1,160 @@+% Copyright (C) 2002-2004,2007 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\darcsCommand{rollback}+\begin{code}+{-# OPTIONS_GHC -cpp #-}+{-# LANGUAGE CPP #-}++module Darcs.Commands.Rollback ( rollback ) where++import Control.Monad ( when, filterM )+import System.Exit ( exitWith, ExitCode(..) )+import Data.List ( sort )+import Data.Maybe ( isJust )+import System.Directory ( removeFile )++import Darcs.Commands ( DarcsCommand(..), nodefaults, loggers )+import Darcs.Arguments ( DarcsFlag(MarkConflicts), fixSubPaths, get_author,+ definePatches,+ working_repo_dir, nocompress,+ author, patchname_option, ask_long_comment,+ leave_test_dir, notest, list_registered_files,+ match_several_or_last, all_interactive, umask_option+ )+import Darcs.RepoPath ( toFilePath )+import Darcs.Repository ( amInRepository, withRepoLock, ($-), applyToWorking,+ read_repo, slurp_recorded,+ tentativelyMergePatches, withGutsOf,+ finalizeRepositoryChanges, sync_repo )+import Darcs.Patch ( summary, invert, namepatch, effect, fromPrims,+ sort_coalesceFL, canonize )+import Darcs.Ordered+import Darcs.Hopefully ( n2pia )+import Darcs.Lock ( world_readable_temp )+import Darcs.SlurpDirectory ( empty_slurpy, wait_a_moment )+import Darcs.Match ( first_match )+import Darcs.SelectChanges ( with_selected_last_changes_to_files_reversed,+ with_selected_last_changes_to_files' )+import Darcs.Commands.Record ( file_exists, get_log )+import Darcs.Commands.Unrecord ( get_last_patches )+import Darcs.Utils ( clarify_errors )+import Printer ( renderString )+import Progress ( debugMessage )+import Darcs.Sealed ( Sealed(..), FlippedSeal(..) )+import IsoDate ( getIsoDateTime )+import Darcs.Gorsvet( invalidateIndex )+#include "impossible.h"++rollback_description :: String+rollback_description =+ "Record a new patch reversing some recorded changes."+\end{code}++If you decide you didn't want to roll back a patch+after all, you can reverse its effect by obliterating the rolled-back patch.++Rollback can actually allow you to roll back a subset of the changes made+by the selected patch or patches. Many of the options available in+rollback behave similarly to the options for unrecord~\ref{unrecord} and+record~\ref{record}.++\begin{code}+rollback_help :: String+rollback_help =+ "Rollback is used to undo the effects of one or more patches without actually\n"+++ "deleting them. Instead, it creates a new patch reversing selected portions.\n"+++ "of those changes. Unlike obliterate and unrecord (which accomplish a similar\n"+++ "goal) rollback is perfectly safe, since it leaves in the repository a record\n"+++ "of its changes.\n"++rollback :: DarcsCommand+rollback = DarcsCommand {command_name = "rollback",+ command_help = rollback_help,+ command_description = rollback_description,+ command_extra_args = -1,+ command_extra_arg_help = ["[FILE or DIRECTORY]..."],+ command_command = rollback_cmd,+ command_prereq = amInRepository,+ command_get_arg_possibilities = list_registered_files,+ command_argdefaults = nodefaults,+ command_advanced_options = [nocompress,umask_option],+ command_basic_options = [match_several_or_last,+ all_interactive,+ author, patchname_option, ask_long_comment,+ notest, leave_test_dir,+ working_repo_dir]}++rollback_cmd :: [DarcsFlag] -> [String] -> IO ()+rollback_cmd opts args = withRepoLock opts $- \repository -> do+ let (logMessage,_,_) = loggers opts+ rec <- if null args then return empty_slurpy+ else slurp_recorded repository+ files <- sort `fmap` fixSubPaths opts args+ existing_files <- map toFilePath `fmap` filterM (file_exists rec) files+ non_existent_files <- map toFilePath `fmap` filterM (fmap not . file_exists rec) files+ when (not $ null existing_files) $+ logMessage $ "Recording changes in "++unwords existing_files++":\n"+ when (not $ null non_existent_files) $+ logMessage $ "Non existent files or directories: "++unwords non_existent_files++"\n"+ when ((not $ null non_existent_files) && null existing_files) $+ fail "None of the files you specified exist!"+ allpatches <- read_repo repository+ FlippedSeal patches <- return $ if first_match opts+ then get_last_patches opts allpatches+ else FlippedSeal $ concatRL allpatches+ with_selected_last_changes_to_files_reversed "rollback" opts existing_files+ (reverseRL patches) $+ \ (_ :> ps) ->+ do when (nullFL ps) $ do logMessage "No patches selected!"+ exitWith ExitSuccess+ definePatches ps+ with_selected_last_changes_to_files' "rollback" opts+ existing_files (concatFL $ mapFL_FL canonize $+ sort_coalesceFL $ effect ps) $ \ (_:>ps'') ->+ do when (nullFL ps'') $ do logMessage "No changes selected!"+ exitWith ExitSuccess+ let make_log = world_readable_temp "darcs-rollback"+ newlog = Just ("", "":"rolling back:":"":lines (renderString $ summary ps ))+ --tentativelyRemovePatches repository opts (mapFL_FL hopefully ps)+ (name, my_log, logf) <- get_log opts newlog make_log $ invert ps''+ date <- getIsoDateTime+ my_author <- get_author opts+ rbp <- n2pia `fmap` namepatch date name my_author my_log+ (fromPrims $ invert ps'')+ debugMessage "Adding rollback patch to repository."+ Sealed pw <- tentativelyMergePatches repository "rollback" (MarkConflicts : opts)+ NilFL (rbp :>: NilFL)+ debugMessage "Finalizing rollback changes..."+ invalidateIndex repository+ withGutsOf repository $ do+ finalizeRepositoryChanges repository+ debugMessage "About to apply rolled-back changes to working directory."+ -- so work will be more recent than rec:+ revertable $ do wait_a_moment+ applyToWorking repository opts pw+ when (isJust logf) $ removeFile (fromJust logf)+ sync_repo repository+ logMessage $ "Finished rolling back."+ where revertable x = x `clarify_errors` unlines+ ["Error applying patch to the working directory.","",+ "This may have left your working directory an inconsistent",+ "but recoverable state. If you had no un-recorded changes",+ "by using 'darcs revert' you should be able to make your",+ "working directory consistent again."]+\end{code}+
@@ -0,0 +1,444 @@+% Copyright (C) 2002-2004 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\darcsCommand{send}+\begin{code}+{-# OPTIONS_GHC -cpp #-}+{-# LANGUAGE CPP #-}++module Darcs.Commands.Send ( send ) where+import Data.Char ( isAlpha, isDigit, isSpace, toLower )+import System.Exit ( exitWith, ExitCode( ExitSuccess ) )+import System.IO.Error ( ioeGetErrorString )+import System.IO ( hClose )+import Control.Monad ( when, unless, forM_ )+import Data.Maybe ( isJust, isNothing )++import Darcs.Commands ( DarcsCommand(..) )+import Darcs.Arguments ( DarcsFlag( EditDescription, LogFile, RmLogFile,+ Target, OutputAutoName, Output, Context,+ DryRun, Verbose, Quiet, Unified+ ),+ fixUrl, definePatches,+ get_cc, get_author, working_repo_dir,+ edit_description, logfile, rmlogfile,+ sign, get_subject, deps_sel, get_in_reply_to,+ match_several, set_default, output_auto_name,+ output, cc, subject, target, author, sendmail_cmd,+ in_reply_to, remote_repo, network_options,+ all_interactive, get_sendmail_cmd,+ print_dry_run_message_and_exit,+ summary, allow_unrelated_repos,+ from_opt, dry_run, send_to_context,+ )+import Darcs.Hopefully ( PatchInfoAnd, hopefully, info )+import Darcs.Repository ( PatchSet, Repository,+ amInRepository, identifyRepositoryFor, withRepoReadLock, ($-),+ read_repo, slurp_recorded, prefsUrl, checkUnrelatedRepos )+import Darcs.Patch ( RepoPatch, description, apply_to_slurpy, invert )+import Darcs.Ordered ( FL(..), RL(..), (:>)(..), (:\/:)(..), unsafeUnRL,+ mapRL_RL, mapFL, mapRL, reverseRL, mapFL_FL, lengthFL, nullFL )+import Darcs.Patch.Bundle ( make_bundle, scan_context )+import Darcs.Patch.Info ( just_name )+import Darcs.Repository.Prefs ( defaultrepo, set_defaultrepo, get_preflist )+import Darcs.External ( signString, sendEmailDoc, fetchFilePS, Cachable(..), generateEmail )+import ByteStringUtils ( mmapFilePS )+import qualified Data.ByteString.Char8 as BC (unpack)+import Darcs.Lock ( withOpenTemp, writeDocBinFile, readDocBinFile, world_readable_temp, removeFileMayNotExist )+import Darcs.SelectChanges ( with_selected_changes )+import Darcs.Patch.Depends ( get_common_and_uncommon )+import Darcs.Utils ( askUser, catchall, edit_file, formatPath )+import Progress ( debugMessage )+import Darcs.Email ( make_email )+import Printer ( Doc, vsep, vcat, text, ($$), putDocLn, putDoc )+import Darcs.RepoPath ( toFilePath, AbsolutePath, AbsolutePathOrStd,+ getCurrentDirectory, makeAbsoluteOrStd, useAbsoluteOrStd )+import HTTP ( postUrl )+#include "impossible.h"++send_description :: String+send_description =+ "Send by email a bundle of one or more patches."++send_help :: String+send_help =+ "Send is used to prepare a bundle of patches that can be applied to a target\n"+++ "repository. Send accepts the URL of the repository as an argument. When\n"+++ "called without an argument, send will use the most recent repository that\n"+++ "was either pushed to, pulled from or sent to. By default, the patch bundle\n"+++ "is sent by email, although you may save it to a file.\n"+\end{code}++Do not confuse the \verb!--author! options with the return address+that \verb!darcs send! will set for your patch bundle.++For example, if you have two email addresses A and B:+\begin{description}+\item If you use+\verb!--author A! but your machine is configured to send mail from+address B by default, then the return address on your message will be B.++\item If you use \verb!--from A! and your mail client supports setting the+From: address arbitrarily (some non-Unix-like mail clients, especially,+may not support this), then the return address will be A; if it does+not support this, then the return address will be B.++\item If you supply neither \verb!--from! nor \verb!--author!, then the return+address will be B.+\end{description}++In addition, unless you specify the sendmail command with+\verb!--sendmail-command!, darcs sends email using the default email+command on your computer. This default command is determined by the+\verb!configure! script. Thus, on some non-Unix-like OSes,+\verb!--from! is likely to not work at all.++\begin{code}+send :: DarcsCommand+send = DarcsCommand {command_name = "send",+ command_help = send_help,+ command_description = send_description,+ command_extra_args = 1,+ command_extra_arg_help = ["[REPOSITORY]"],+ command_command = send_cmd,+ command_prereq = amInRepository,+ command_get_arg_possibilities = get_preflist "repos",+ command_argdefaults = defaultrepo,+ command_advanced_options = [logfile, rmlogfile,+ remote_repo,+ send_to_context] +++ network_options,+ command_basic_options = [match_several, deps_sel,+ all_interactive,+ from_opt, author,+ target,cc,subject, in_reply_to,+ output,output_auto_name,sign]+ ++dry_run++[summary,+ edit_description,+ set_default, working_repo_dir,+ sendmail_cmd,+ allow_unrelated_repos]}++send_cmd :: [DarcsFlag] -> [String] -> IO ()+send_cmd input_opts [""] = send_cmd input_opts []+send_cmd input_opts [unfixedrepodir] = withRepoReadLock input_opts $- \repository -> do+ context_ps <- the_context input_opts+ case context_ps of+ Just them -> send_to_them repository input_opts [] "CONTEXT" them+ Nothing -> do+ repodir <- fixUrl input_opts unfixedrepodir+ -- Test to make sure we aren't trying to push to the current repo+ here <- getCurrentDirectory+ when (repodir == toFilePath here) $+ fail ("Can't send to current repository! Did you mean send -"++"-context?")+ repo <- identifyRepositoryFor repository repodir+ them <- read_repo repo+ old_default <- get_preflist "defaultrepo"+ set_defaultrepo repodir input_opts+ when (old_default == [repodir] && not (Quiet `elem` input_opts)) $+ putStrLn $ "Creating patch to "++formatPath repodir++"..."+ wtds <- decide_on_behavior input_opts repo+ send_to_them repository input_opts wtds repodir them+ where the_context [] = return Nothing+ the_context (Context foo:_)+ = (Just . scan_context )`fmap` mmapFilePS (toFilePath foo)+ the_context (_:fs) = the_context fs+send_cmd _ _ = impossible++send_to_them :: RepoPatch p => Repository p -> [DarcsFlag] -> [WhatToDo] -> String -> PatchSet p -> IO ()+send_to_them repo opts wtds their_name them = do+ let am_verbose = Verbose `elem` opts+ am_quiet = Quiet `elem` opts+ putVerbose s = when am_verbose $ putDocLn s+ putInfo s = when (not am_quiet) $ putStrLn s+ patch_desc p = just_name $ info p+ make_fname tbs = patch_filename $ patch_desc $ headFL tbs+ headFL (x:>:_) = x+ headFL _ = impossible+ us <- read_repo repo+ case get_common_and_uncommon (us, them) of+ (common, us' :\/: _) -> do+ checkUnrelatedRepos opts common us them+ case us' of+ NilRL:<:NilRL -> do putInfo "No recorded local changes to send!"+ exitWith ExitSuccess+ _ -> putVerbose $ text "We have the following patches to send:"+ $$ (vcat $ mapRL description $ head $ unsafeUnRL us')+ s <- slurp_recorded repo+ let our_ps = reverseRL $ head $ unsafeUnRL us'+ with_selected_changes "send" opts our_ps $+ \ (to_be_sent :> _) -> do+ print_dry_run_message_and_exit "send" opts to_be_sent+ when (nullFL to_be_sent) $ do+ putInfo "You don't want to send any patches, and that's fine with me!"+ exitWith ExitSuccess+ definePatches to_be_sent+ bundle <- signString opts $ make_bundle (Unified:opts)+ (fromJust $ apply_to_slurpy+ (invert $+ mapRL_RL hopefully $ head $ unsafeUnRL us') s)+ common (mapFL_FL hopefully to_be_sent)+ let outname = get_output opts (make_fname to_be_sent)+ case outname of+ Just fname -> do (d,f) <- get_description opts to_be_sent+ let putabs a = do writeDocBinFile a (d $$ bundle)+ putStrLn $ "Wrote patch to " ++ toFilePath a ++ "."+ putstd = putDoc (d $$ bundle)+ useAbsoluteOrStd putabs putstd fname+ cleanup f+ Nothing ->+ let+ auto_subject (p:>:NilFL) = "darcs patch: " ++ trim (patch_desc p) 57+ auto_subject (p:>:ps) = "darcs patch: " ++ trim (patch_desc p) 43 +++ " (and " ++ show (lengthFL ps) ++ " more)"+ auto_subject _ = error "Tried to get a name from empty patch list."+ trim st n = if length st <= n then st+ else take (n-3) st ++ "..."+ in do+ thetargets <- get_targets wtds+ from <- get_author opts+ let thesubject = case get_subject opts of+ Nothing -> auto_subject to_be_sent+ Just subj -> subj+ (mailcontents, mailfile) <- get_description opts to_be_sent+ let body = make_email their_name+ (maybe [] (\x -> [("In-Reply-To", x), ("References", x)]) . get_in_reply_to $ opts)+ (Just mailcontents)+ bundle+ (Just $ make_fname to_be_sent)+ contentAndBundle = Just (mailcontents, bundle)+ + sendmail = do+ sm_cmd <- get_sendmail_cmd opts+ (sendEmailDoc from (lt [t | SendMail t <- thetargets]) (thesubject) (get_cc opts)+ sm_cmd contentAndBundle body >>+ putInfo ("Successfully sent patch bundle to: "+ ++ lt [ t | SendMail t <- thetargets ]+ ++ ccs (get_cc opts) ++"."))+ `catch` \e -> let msg = "Email body left in " in+ do case mailfile of+ Just mf -> putStrLn $ msg++mf++"."+ Nothing -> return ()+ fail $ ioeGetErrorString e+ ccs [] = []+ ccs cs = " and cc'ed " ++ cs++ when (null [ p | Post p <- thetargets]) sendmail+ nbody <- withOpenTemp $ \ (fh,fn) -> do+ generateEmail fh from (lt [t | SendMail t <- thetargets]) thesubject (get_cc opts) body+ hClose fh+ mmapFilePS fn+ forM_ [ p | Post p <- thetargets]+ (\url -> do+ putInfo $ "Posting patch to " ++ url+ postUrl url (BC.unpack nbody) "message/rfc822")+ `catch` const sendmail+ cleanup mailfile++ where cleanup (Just mailfile) = when (isNothing (get_fileopt opts) || (RmLogFile `elem` opts)) $+ removeFileMayNotExist mailfile+ cleanup Nothing = return ()+ lt [t] = t+ lt [t,""] = t+ lt (t:ts) = t++" , "++lt ts+ lt [] = ""++safeFileChar :: Char -> Char+safeFileChar c | isAlpha c = toLower c+ | isDigit c = c+ | isSpace c = '-'+safeFileChar _ = '_'++patch_filename :: String -> String+patch_filename the_summary = name ++ ".dpatch"+ where name = map safeFileChar the_summary+\end{code}++\begin{options}+--output, --to, --cc+\end{options}++The \verb!--output!, \verb!--output-auto-name!, and \verb!--to! flags determine+what darcs does with the patch bundle after creating it. If you provide an+\verb!--output! argument, the patch bundle is saved to that file. If you+specify \verb!--output-auto-name!, the patch bundle is saved to a file with an+automatically generated name. If you give one or more \verb!--to! arguments,+the bundle of patches is sent to those locations. The locations may either be email+addresses or urls that the patch should be submitted to via HTTP.++If you don't provide any of these options, darcs will look at the contents of+the \verb!_darcs/prefs/email! file in the target repository (if it exists), and+send the patch by email to that address. In this case, you may use the+\verb!--cc! option to specify additional recipients without overriding the+default repository email address.++If \texttt{\_darcs/prefs/post} exists in the target repository, darcs will+upload to the URL contained in that file, which may either be a+\texttt{mailto:} URL, or an \texttt{http://} URL. In the latter case, the+patch is posted to that URL.++If there is no email address associated with the repository, darcs will+prompt you for an email address.++\begin{options}+--subject+\end{options}++Use the \verb!--subject! flag to set the subject of the e-mail to be sent.+If you don't provide a subject on the command line, darcs will make one up+based on names of the patches in the patch bundle.++\begin{options}+--in-reply-to+\end{options}++Use the \verb!--in-reply-to! flag to set the In-Reply-To and References headers+of the e-mail to be sent. By default no additional headers are included so e-mail+will not be treated as reply by mail readers.++\begin{code}++data WhatToDo+ = Post String -- ^ POST the patch via HTTP+ | SendMail String -- ^ send patch via email+++decide_on_behavior :: RepoPatch p => [DarcsFlag] -> Repository p -> IO [WhatToDo]+decide_on_behavior opts the_remote_repo =+ case the_targets of+ [] ->+ if isJust $ get_output opts ""+ then return []+ else+ do wtds <- check_post+ unless (null wtds) $ announce_recipients wtds+ return wtds+ ts -> do announce_recipients ts+ return ts+ where the_targets = collect_targets opts+#ifdef HAVE_HTTP+ -- the ifdef above is to so that darcs only checks the remote+ -- _darcs/post if we have an implementation of postUrl. See+ -- our HTTP module for more details+ check_post = do p <- ((readPost . BC.unpack) `fmap`+ fetchFilePS (prefsUrl the_remote_repo++"/post")+ (MaxAge 600)) `catchall` return []+ emails <- who_to_email+ return (p++emails)+ readPost p = map pp (lines p) where+ pp ('m':'a':'i':'l':'t':'o':':':s) = SendMail s+ pp s = Post s+#else+ check_post = who_to_email+#endif+ who_to_email =+ do email <- (BC.unpack `fmap`+ fetchFilePS (prefsUrl the_remote_repo++"/email")+ (MaxAge 600))+ `catchall` return ""+ if '@' `elem` email then return . map SendMail $ lines email+ else return []+ putInfoLn s = unless (Quiet `elem` opts) $ putStrLn s+ announce_recipients emails =+ let pn (SendMail s) = s+ pn (Post p) = p+ in if DryRun `elem` opts+ then putInfoLn $ "Patch bundle would be sent to: "++unwords (map pn emails)+ else when (null the_targets) $+ putInfoLn $ "Patch bundle will be sent to: "++unwords (map pn emails)++get_output :: [DarcsFlag] -> FilePath -> Maybe AbsolutePathOrStd+get_output (Output a:_) _ = return a+get_output (OutputAutoName a:_) f = return $ makeAbsoluteOrStd a f+get_output (_:flags) f = get_output flags f+get_output [] _ = Nothing++get_targets :: [WhatToDo] -> IO [WhatToDo]+get_targets [] = do fmap ((:[]) . SendMail) $ askUser "What is the target email address? "+get_targets wtds = return wtds++collect_targets :: [DarcsFlag] -> [WhatToDo]+collect_targets flags = [ f t | Target t <- flags ] where+ f url@('h':'t':'t':'p':':':_) = Post url+ f em = SendMail em+++\end{code}++\begin{options}+--matches, --patches, --tags, --no-deps+\end{options}++The \verb!--patches!, \verb!--matches!, \verb!--tags!, and \verb!--no-deps!+options can be used to select which patches to send, as described in+subsection~\ref{selecting}.++\begin{options}+--edit-description+\end{options}++If you want to include a description or explanation along with the bundle+of patches, you need to specify the \verb!--edit-description! flag, which+will cause darcs to open up an editor with which you can compose a message+to go along with your patches.++\begin{options}+--sendmail-command+\end{options}++If you want to use a command different from the default one for sending email,+you need to specify a command line with the \verb!--sendmail-command! option. The+command line can contain some format specifiers which are replaced by the actual+values. Accepted format specifiers are \verb!%s! for subject, \verb!%t! for to,+\verb!%c! for cc, \verb!%b! for the body of the mail, \verb!%f! for from, \verb!%a!+for the patch bundle and the same specifiers in uppercase for the URL-encoded values.+Additionally you can add \verb!%<! to the end of the command line if the command+expects the complete email message on standard input. E.g.\ the command lines for evolution+and msmtp look like this:++\begin{verbatim}+evolution "mailto:%T?subject=%S&attach=%A&cc=%C&body=%B"+msmtp -t %<+\end{verbatim}++\begin{code}+get_description :: RepoPatch p => [DarcsFlag] -> FL (PatchInfoAnd p) -> IO (Doc, Maybe String)+get_description opts patches =+ case get_filename of+ Just f -> do file <- f+ when (EditDescription `elem` opts) $ do+ when (isNothing $ get_fileopt opts) $+ writeDocBinFile file patchdesc+ debugMessage $ "About to edit file " ++ file+ edit_file file+ return ()+ doc <- readDocBinFile file+ return (doc, Just file)+ Nothing -> return (patchdesc, Nothing)+ where patchdesc = vsep $ mapFL description patches+ get_filename = case get_fileopt opts of+ Just f -> Just $ return $ toFilePath f+ Nothing -> if EditDescription `elem` opts+ then Just tempfile+ else Nothing+ tempfile = world_readable_temp "darcs-temp-mail"++get_fileopt :: [DarcsFlag] -> Maybe AbsolutePath+get_fileopt (LogFile f:_) = Just f+get_fileopt (_:flags) = get_fileopt flags+get_fileopt [] = Nothing+\end{code}
@@ -0,0 +1,108 @@+% Copyright (C) 2003 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\darcsCommand{setpref}+\begin{code}+{-# OPTIONS_GHC -cpp #-}+{-# LANGUAGE CPP #-}++module Darcs.Commands.SetPref ( setpref ) where++import System.Exit ( exitWith, ExitCode(..) )+import Control.Monad (when)++import Darcs.Commands ( DarcsCommand(..), nodefaults )+import Darcs.Arguments ( DarcsFlag, working_repo_dir, umask_option )+import Darcs.Repository ( amInRepository, add_to_pending, withRepoLock, ($-) )+import Darcs.Patch ( changepref )+import Darcs.Ordered ( FL(..) )+import Darcs.Repository.Prefs ( get_prefval, change_prefval, )+import English ( orClauses )+#include "impossible.h"++-- | A list of all valid preferences for @_darcs/prefs/prefs@.+valid_pref_data :: [(String, String)] -- ^ (name, one line description)+valid_pref_data =+ [("test", "a shell command that runs regression tests"),+ ("predist", "a shell command to run before `darcs dist'"),+ ("boringfile", "the path to a version-controlled boring file"),+ ("binariesfile", "the path to a version-controlled binaries file")]++valid_prefs :: [String]+valid_prefs = map fst valid_pref_data++setpref_description :: String+setpref_description =+ "Set a preference (" ++ orClauses valid_prefs ++ ")."++setpref_help :: String+setpref_help =+ "When working on project with multiple repositories and contributors,\n" +++ "it is sometimes desirable for a preference to be set consistently\n" +++ "project-wide. This is achieved by treating a preference set with\n" +++ "`darcs setpref' as an unrecorded change, which can then be recorded\n" +++ "and then treated like any other patch.\n" +++ "\n" +++ "Valid preferences are:\n" +++ "\n" +++ unlines [" "++x++" -- "++y | (x,y) <- valid_pref_data] +++ "\n" +++ "For example, a project using GNU autotools, with a `make test' target\n" +++ "to perform regression tests, might enable Darcs' integrated regression\n" +++ "testing with the following command:\n" +++ "\n" +++ " darcs setpref test 'autoconf && ./configure && make && make test'\n" +++ "\n" +++ "Note that merging is not currently implemented for preferences: if two\n" +++ "patches attempt to set the same preference, the last patch applied to\n" +++ "the repository will always take precedence. This is considered a\n" +++ "low-priority bug, because preferences are seldom set.\n"++setpref :: DarcsCommand+setpref = DarcsCommand {command_name = "setpref",+ command_help = setpref_help,+ command_description = setpref_description,+ command_extra_args = 2,+ command_extra_arg_help = ["<PREF>",+ "<VALUE>"],+ command_command = setpref_cmd,+ command_prereq = amInRepository,+ command_get_arg_possibilities = return valid_prefs,+ command_argdefaults = nodefaults,+ command_advanced_options = [umask_option],+ command_basic_options =+ [working_repo_dir]}++setpref_cmd :: [DarcsFlag] -> [String] -> IO ()+setpref_cmd opts [pref,val] = withRepoLock opts $- \repository -> do+ when (' ' `elem` pref) $ do+ putStrLn $ "'"++pref+++ "' is not a valid preference name: no spaces allowed!"+ exitWith $ ExitFailure 1+ when (not $ pref `elem` valid_prefs) $ do+ putStrLn $ "'"++pref++"' is not a valid preference name!"+ putStrLn $ "Try one of: " ++ unwords valid_prefs+ exitWith $ ExitFailure 1+ oval <- get_prefval pref+ old <- case oval of Just v -> return v+ Nothing -> return ""+ change_prefval pref old val+ putStrLn $ "Changing value of "++pref++" from '"++old++"' to '"++val++"'"+ add_to_pending repository (changepref pref old val :>: NilFL)+setpref_cmd _ _ = impossible+\end{code}+
@@ -0,0 +1,77 @@+% Copyright (C) 2002-2004 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\begin{code}+module Darcs.Commands.Show ( show_command, list, query ) where++import Darcs.Commands ( DarcsCommand(..),+ CommandControl(Command_data, Hidden_command),+ command_alias,+ )+import Darcs.Commands.ShowAuthors ( show_authors )+import Darcs.Commands.ShowBug ( show_bug )+import Darcs.Commands.ShowContents ( show_contents )+import Darcs.Commands.ShowFiles ( show_files, show_manifest )+import Darcs.Commands.ShowTags ( show_tags )+import Darcs.Commands.ShowRepo ( show_repo )+import Darcs.Commands.ShowIndex ( show_index, show_pristine )+import Darcs.Repository ( amInRepository )++show_description :: String+show_description = "Show information which is stored by darcs."++show_help :: String+show_help =+ "Use the --help option with the subcommands to obtain help for\n"+++ "subcommands (for example, \"darcs show files --help\").\n" +++ "\n" +++ "In previous releases, this command was called `darcs query'.\n" +++ "Currently this is a deprecated alias.\n"++show_command :: DarcsCommand+show_command = SuperCommand {command_name = "show",+ command_help = show_help,+ command_description = show_description,+ command_prereq = amInRepository,+ command_sub_commands = [Hidden_command show_bug,+ Command_data show_contents,+ Command_data show_files, Hidden_command show_manifest,+ Command_data show_index,+ Command_data show_pristine,+ Command_data show_repo,+ Command_data show_authors,+ Command_data show_tags]+ }++query :: DarcsCommand+query = command_alias "query" show_command++list :: DarcsCommand+list = command_alias "list" show_command+\end{code}++\subsection{darcs show}++The show command provides access to several subcommands which can be+used to investigate the state of a repository.++\input{Darcs/Commands/ShowAuthors.lhs}+\input{Darcs/Commands/ShowContents.lhs}+\input{Darcs/Commands/ShowFiles.lhs}+\input{Darcs/Commands/ShowTags.lhs}+\input{Darcs/Commands/ShowRepo.lhs}+
@@ -0,0 +1,160 @@+% Copyright (C) 2004-2009 David Roundy, Eric Kow, Simon Michael+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\darcsCommand{show authors}+\begin{code}+{-# OPTIONS_GHC -cpp #-}+module Darcs.Commands.ShowAuthors ( show_authors ) where++import Control.Arrow ((&&&), (***))+import Data.List ( sort, sortBy, group, groupBy, isInfixOf, isPrefixOf )+import Data.Ord (comparing)+import Data.Char ( toLower, isSpace )+import Text.Regex ( Regex, mkRegexWithOpts, matchRegex )++import Darcs.Arguments ( DarcsFlag(..), working_repo_dir )+import Darcs.Commands ( DarcsCommand(..), nodefaults )+import Darcs.External ( viewDoc )+import Darcs.Hopefully ( info )+import Darcs.Repository ( amInRepository, read_repo, withRepository, ($-) )+import Darcs.Patch.Info ( pi_author )+import Darcs.Ordered ( mapRL, concatRL )+import Printer ( text )+import Data.Function (on)++show_authors_description :: String+show_authors_description = "List authors by patch count."++show_authors_help :: String+show_authors_help =+ "The `darcs show authors' command lists the authors of the current\n" +++ "repository, sorted by the number of patches contributed. With the\n" +++ "--verbose option, this command simply lists the author of each patch\n" +++ "(without aggregation or sorting).\n" +++ "\n" +++ "An author's name or email address may change over time. To tell Darcs\n" +++ "when multiple author strings refer to the same individual, create an\n" +++ "`.authorspellings' file in the root of the working tree. Each line in\n" +++ "this file begins with an author's canonical name and address, and may\n" +++ "be followed by a comma and zero or more extended regular expressions,\n" +++ "separated by commas. Blank lines and lines beginning with two\n" +++ "hyphens, are ignored.\n" +++ "\n" +++ "Any patch with an author string that matches the canonical address or\n" +++ "any of the associated regexps is considered to be the work of that\n" +++ "author. All matching is case-insensitive and partial (it can match a\n" +++ "substring).\n" +++ "\n" +++ "Currently this canonicalization step is done only in `darcs show\n" +++ "authors'. Other commands, such as `darcs changes' use author strings\n" +++ "verbatim.\n" +++ "\n" +++ "An example .authorspelling file is:\n" +++ "\n" +++ " -- This is a comment.\n" +++ " Fred Nurk <fred@example.com>\n" +++ " John Snagge <snagge@bbc.co.uk>, John, snagge@, js@(si|mit).edu\n"++show_authors :: DarcsCommand+show_authors = DarcsCommand {+ command_name = "authors",+ command_help = show_authors_help,+ command_description = show_authors_description,+ command_extra_args = 0,+ command_extra_arg_help = [],+ command_command = authors_cmd,+ command_prereq = amInRepository,+ command_get_arg_possibilities = return [],+ command_argdefaults = nodefaults,+ command_advanced_options = [],+ command_basic_options = [working_repo_dir] }++authors_cmd :: [DarcsFlag] -> [String] -> IO ()+authors_cmd opts _ = withRepository opts $- \repository -> do+ patches <- read_repo repository+ spellings <- compiled_author_spellings+ let authors = mapRL (pi_author . info) $ concatRL patches+ viewDoc $ text $ unlines $+ if Verbose `elem` opts+ then authors+ else -- A list of the form ["<count> <canonical name>"]...++ -- Turn the final result into a list of strings.+ map (\ (count, name) -> show count ++ "\t" ++ name) $+ -- Sort by descending patch count.+ reverse $ sortBy (comparing fst) $+ -- Combine duplicates from a list [(count, canonized name)]+ -- with duplicates canonized names (see next comment).+ map ((sum *** head) . unzip) $+ groupBy ((==) `on` snd) $+ sortBy (comparing snd) $+ -- Because it would take a long time to canonize "foo" into+ -- "foo <foo@bar.baz>" once per patch, the code below+ -- generates a list [(count, canonized name)].+ map (length &&& (canonize_author spellings . head)) $+ group $ sort authors++canonize_author :: [(String,[Regex])] -> String -> String+canonize_author [] a = a+canonize_author spellings a = safehead a $ canonicalsfor a+ where+ safehead x xs = if null xs then x else head xs+ canonicalsfor s = map fst $ filter (ismatch s) spellings+ ismatch s (canonical,regexps) =+ (not (null email) && (s `contains` email)) || (any (s `contains_regex`) regexps)+ where email = takeWhile (/= '>') $ drop 1 $ dropWhile (/= '<') canonical++contains :: String -> String -> Bool+a `contains` b = lower b `isInfixOf` (lower a) where lower = map toLower++contains_regex :: String -> Regex -> Bool+a `contains_regex` r = case matchRegex r a of+ Just _ -> True+ _ -> False++compiled_author_spellings :: IO [(String,[Regex])]+compiled_author_spellings = do+ ss <- author_spellings_from_file+ return $ map compile $ ss+ where+ compile [] = error "each author spelling should contain at least the canonical form"+ compile (canonical:pats) = (canonical, map mkregex pats)+ mkregex pat = mkRegexWithOpts pat True False++authorspellingsfile :: FilePath+authorspellingsfile = ".authorspellings"++author_spellings_from_file :: IO [[String]]+author_spellings_from_file = do+ s <- readFile -- ratify readFile: never unlinked from within darcs+ authorspellingsfile `catch` (\_ -> return "")+ let noncomments = filter (not . ("--" `isPrefixOf`)) $+ filter (not . null) $ map strip $ lines s+ return $ map (map strip . split_on ',') noncomments++split_on :: Eq a => a -> [a] -> [[a]]+split_on e l =+ case dropWhile (e==) l of+ [] -> []+ l' -> first : split_on e rest+ where+ (first,rest) = break (e==) l'++strip :: String -> String+strip = dropWhile isSpace . reverse . dropWhile isSpace . reverse++\end{code}
@@ -0,0 +1,53 @@+% Copyright (C) 2007 Eric Kow+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\darcsCommand{show bug}+\begin{code}+{-# OPTIONS_GHC -cpp #-}+{-# LANGUAGE CPP #-}++module Darcs.Commands.ShowBug ( show_bug ) where++import Darcs.Commands ( DarcsCommand(..), nodefaults )+import Darcs.Arguments ( DarcsFlag, working_repo_dir )+import Darcs.Repository ( findRepository )+#include "impossible.h"++show_bug_description :: String+show_bug_description = "Simulate a run-time failure."++show_bug_help :: String+show_bug_help =+ "Show bug can be used to see what darcs would show you if you encountered.\n"+ ++"a bug in darcs.\n"++show_bug :: DarcsCommand+show_bug = DarcsCommand {command_name = "bug",+ command_help = show_bug_help,+ command_description = show_bug_description,+ command_extra_args = 0,+ command_extra_arg_help = [],+ command_command = show_bug_cmd,+ command_prereq = findRepository,+ command_get_arg_possibilities = return [],+ command_argdefaults = nodefaults,+ command_advanced_options = [],+ command_basic_options = [working_repo_dir]}++show_bug_cmd :: [DarcsFlag] -> [String] -> IO ()+show_bug_cmd _ _ = bug "This is actually a fake bug in darcs."+\end{code}
@@ -0,0 +1,72 @@+% Copyright (C) 2007 Eric Kow+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\darcsCommand{show contents}+\begin{code}+module Darcs.Commands.ShowContents ( show_contents ) where++import Control.Monad ( filterM )+import System.IO ( stdout )+import System.FilePath.Posix ( takeFileName )++import qualified Data.ByteString as B+import Workaround ( getCurrentDirectory )++import Darcs.Commands ( DarcsCommand(..), nodefaults )+import Darcs.Arguments ( DarcsFlag, match_one,+ working_repo_dir, fixSubPaths )+import Darcs.RepoPath ( toFilePath, sp2fn )+import Darcs.IO ( mReadFilePS, mDoesFileExist )+import Darcs.Match ( get_partial_nonrange_match, have_nonrange_match )+import Darcs.Repository ( withRepository, ($-), findRepository,+ createPartialsPristineDirectoryTree )+import Darcs.Lock ( withTempDir )++show_contents_description :: String+show_contents_description = "Outputs a specific version of a file."++show_contents_help :: String+show_contents_help =+ "Show contents can be used to display an earlier version of some file(s).\n"+++ "If you give show contents no version arguments, it displays the recorded\n"+++ "version of the file(s).\n"++show_contents :: DarcsCommand+show_contents = DarcsCommand {command_name = "contents",+ command_help = show_contents_help,+ command_description = show_contents_description,+ command_extra_args = -1,+ command_extra_arg_help+ = ["[FILE]..."],+ command_command = show_contents_cmd,+ command_prereq = findRepository,+ command_get_arg_possibilities = return [],+ command_argdefaults = nodefaults,+ command_advanced_options = [],+ command_basic_options = [match_one, working_repo_dir]}++show_contents_cmd :: [DarcsFlag] -> [String] -> IO ()+show_contents_cmd opts args = withRepository opts $- \repository -> do+ formerdir <- getCurrentDirectory+ path_list <- map sp2fn `fmap` fixSubPaths opts args+ thename <- return $ takeFileName formerdir+ withTempDir thename $ \dir -> do+ if have_nonrange_match opts+ then get_partial_nonrange_match repository opts path_list+ else createPartialsPristineDirectoryTree repository path_list (toFilePath dir)+ filterM mDoesFileExist path_list >>= mapM_ (\f -> mReadFilePS f >>= B.hPut stdout)+\end{code}
@@ -0,0 +1,99 @@+% Copyright (C) 2005 Florian Weimer+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\darcsCommand{show files}+\begin{code}+{-# OPTIONS_GHC -cpp #-}+{-# LANGUAGE CPP #-}+#include "gadts.h"+module Darcs.Commands.ShowFiles ( show_files, show_manifest ) where+import Darcs.Arguments ( DarcsFlag(..), working_repo_dir,+ files, directories, pending, nullFlag )+import Darcs.Commands ( DarcsCommand(..), nodefaults, command_alias )+import Darcs.Repository ( Repository, amInRepository, slurp_pending, slurp_recorded,+ withRepository )+import Darcs.Patch ( RepoPatch )+import Darcs.SlurpDirectory ( Slurpy, list_slurpy, list_slurpy_files, list_slurpy_dirs )++show_files_description :: String+show_files_description = "Show version-controlled files in the working copy."++show_files_help :: String+show_files_help =+ "The `darcs show files' command lists those files and directories in\n" +++ "the working tree that are under version control. This command is\n" +++ "primarily for scripting purposes; end users will probably want `darcs\n" +++ "whatsnew --summary'.\n" +++ "\n" +++ "A file is `pending' if it has been added but not recorded. By\n" +++ "default, pending files (and directories) are listed; the --no-pending\n" +++ "option prevents this.\n" +++ "\n" +++ "By default `darcs show files' lists both files and directories, but\n" +++ "the alias `darcs show manifest' only lists files. The --files,\n" +++ "--directories, --no-files and --no-directories modify this behaviour.\n" +++ "\n" +++ "By default entries are one-per-line (i.e. newline separated). This\n" +++ "can cause problems if the files themselves contain newlines or other\n" +++ "control characters. To get aroudn this, the --null option uses the\n" +++ "null character instead. The script interpreting output from this\n" +++ "command needs to understand this idiom; `xargs -0' is such a command.\n" +++ "\n" +++ "For example, to list version-controlled files by size:\n" +++ "\n" +++ " darcs show files -0 | xargs -0 ls -ldS\n"++show_files :: DarcsCommand+show_files = DarcsCommand {+ command_name = "files",+ command_help = show_files_help,+ command_description = show_files_description,+ command_extra_args = 0,+ command_extra_arg_help = [],+ command_command = manifest_cmd to_list_files,+ command_prereq = amInRepository,+ command_get_arg_possibilities = return [],+ command_argdefaults = nodefaults,+ command_advanced_options = [],+ command_basic_options = [files, directories, pending, nullFlag,+ working_repo_dir] }++show_manifest :: DarcsCommand+show_manifest = command_alias "manifest" show_files {+ command_command = manifest_cmd to_list_manifest+}++to_list_files, to_list_manifest :: [DarcsFlag] -> Slurpy -> [FilePath]+to_list_files opts = files_dirs (NoFiles `notElem` opts) (NoDirectories `notElem` opts)+to_list_manifest opts = files_dirs (NoFiles `notElem` opts) (Directories `elem` opts)++files_dirs :: Bool -> Bool -> Slurpy -> [FilePath]+files_dirs False False = \_ -> []+files_dirs False True = list_slurpy_dirs+files_dirs True False = list_slurpy_files+files_dirs True True = list_slurpy++manifest_cmd :: ([DarcsFlag] -> Slurpy -> [FilePath]) -> [DarcsFlag] -> [String] -> IO ()+manifest_cmd to_list opts _ = do+ list <- (to_list opts) `fmap` withRepository opts slurp+ mapM_ output list+ where slurp :: RepoPatch p => Repository p C(r u r) -> IO Slurpy+ slurp = if NoPending `notElem` opts+ then slurp_pending else slurp_recorded+ output_null name = do { putStr name ; putChar '\0' }+ output = if NullFlag `elem` opts then output_null else putStrLn+\end{code}
@@ -0,0 +1,94 @@+% Copyright (C) 2009 Petr Rockai+%+% Permission is hereby granted, free of charge, to any person+% obtaining a copy of this software and associated documentation+% files (the "Software"), to deal in the Software without+% restriction, including without limitation the rights to use, copy,+% modify, merge, publish, distribute, sublicense, and/or sell copies+% of the Software, and to permit persons to whom the Software is+% furnished to do so, subject to the following conditions:+%+% The above copyright notice and this permission notice shall be+% included in all copies or substantial portions of the Software.+%+% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+% MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND+% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS+% BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN+% ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN+% CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+% SOFTWARE.++\darcsCommand{show index}+\begin{code}+{-# OPTIONS_GHC -cpp #-}+{-# LANGUAGE CPP #-}+#include "gadts.h"+module Darcs.Commands.ShowIndex ( show_index, show_pristine ) where+import Darcs.Arguments ( DarcsFlag(..), working_repo_dir,+ files, directories, nullFlag )+import Darcs.Commands ( DarcsCommand(..), nodefaults, command_alias )+import Darcs.Repository ( amInRepository, withRepository, ($-) )++import Darcs.Gorsvet( checkIndex, readIndex )+import Storage.Hashed( readDarcsPristine, floatPath )+import Storage.Hashed.Darcs( darcsFormatHash )+import Storage.Hashed.Tree( list, expand, itemHash, TreeItem( SubTree ) )+import Storage.Hashed.AnchoredPath( anchorPath )++import qualified Data.ByteString.Char8 as BS++show_index :: DarcsCommand+show_index = DarcsCommand {+ command_name = "index",+ command_description = "Dump contents of working tree index.",+ command_help =+ "The `darcs show index' command lists all version-controlled files and " +++ "directories along with their hashes as stored in _darcs/index. " +++ "For files, the fields correspond to file size, sha256 of the current " +++ "file content and the filename.",+ command_extra_args = 0,+ command_extra_arg_help = [],+ command_command = show_index_cmd,+ command_prereq = amInRepository,+ command_get_arg_possibilities = return [],+ command_argdefaults = nodefaults,+ command_advanced_options = [],+ command_basic_options = [files, directories, nullFlag, working_repo_dir] }++show_pristine :: DarcsCommand+show_pristine = command_alias "pristine" show_index {+ command_command = show_pristine_cmd,+ command_description = "Dump contents of pristine cache.",+ command_help =+ "The `darcs show pristine' command lists all version-controlled files " +++ "and directories along with the hashes of their pristine copies. " +++ "For files, the fields correspond to file size, sha256 of the pristine " +++ "file content and the filename." }++dump opts tree = do+ let line | NullFlag `elem` opts = \t -> putStr t >> putChar '\0'+ | otherwise = putStrLn+ output (p, i) = do+ let hash = case itemHash i of+ Just h -> BS.unpack $ darcsFormatHash h+ Nothing -> "(no hash available)"+ path = anchorPath "" p+ isdir = case i of+ SubTree _ -> "/"+ _ -> ""+ line $ hash ++ " " ++ path ++ isdir+ x <- expand tree+ mapM_ output $ (floatPath ".", SubTree x) : list x++show_index_cmd :: [DarcsFlag] -> [String] -> IO ()+show_index_cmd opts _ = withRepository opts $- \repo -> do+ checkIndex repo+ readIndex >>= dump opts++show_pristine_cmd :: [DarcsFlag] -> [String] -> IO ()+show_pristine_cmd opts _ = withRepository opts $- \repo -> do+ readDarcsPristine "." >>= dump opts++\end{code}
@@ -0,0 +1,178 @@+% Copyright (C) 2007 Kevin Quick+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\darcsCommand{show repo}++The \verb!show repo! displays information about+the current repository: the location, the type, etc.++This is provided as informational output for two purposes: curious+users and scripts invoking darcs. For the latter, this information+can be parsed to facilitate the script; for example,+\verb!darcs show repo | grep Root: | awk {print $2}!+can be used to locate the+top-level \verb!_darcs! directory from anyplace within a darcs repository+working directory.++\begin{code}+{-# OPTIONS_GHC -cpp #-}+{-# LANGUAGE CPP #-}+#include "gadts.h"+module Darcs.Commands.ShowRepo ( show_repo ) where++import Data.Char ( toLower, isSpace )+import Data.List ( intersperse )+import Control.Monad ( when, unless )+import Text.Html ( tag, stringToHtml )+import Darcs.Arguments ( DarcsFlag(..), working_repo_dir, files, xmloutput )+import Darcs.Commands ( DarcsCommand(..), nodefaults )+import Darcs.Repository ( withRepository, ($-), amInRepository, read_repo )+import Darcs.Repository.Internal ( Repository(..), RepoType(..) )+import Darcs.Repository.Format ( RepoFormat(..) )+import Darcs.Repository.Prefs ( get_preflist )+import Darcs.Repository.Motd ( get_motd )+import Darcs.Patch ( RepoPatch )+import Darcs.Ordered ( lengthRL, concatRL )+import qualified Data.ByteString.Char8 as BC (unpack)++show_repo_help :: String+show_repo_help =+ "The repo command displays information about the current repository\n" +++ "(location, type, etc.). Some of this information is already available\n" +++ "by inspecting files within the _darcs directory and some is internal\n" +++ "information that is informational only (i.e. for developers). This\n" +++ "command collects all of the repository information into a readily\n" +++ "available source.\n"++show_repo_description :: String+show_repo_description = "Show repository summary information"++show_repo :: DarcsCommand+show_repo = DarcsCommand { command_name = "repo",+ command_help = show_repo_help,+ command_description = show_repo_description,+ command_extra_args = 0,+ command_extra_arg_help = [],+ command_command = repo_cmd,+ command_prereq = amInRepository,+ command_get_arg_possibilities = return [],+ command_argdefaults = nodefaults,+ command_advanced_options = [],+ command_basic_options = [working_repo_dir, files, xmloutput] }+\end{code}++\begin{options}+--files, --no-files+\end{options}++If the \verb!--files! option is specified (the default), then the+\verb!show repo! operation will read patch information from the+repository and display the number of patches in the repository. The+\verb!--no-files! option can be used to suppress this operation (and+improve performance).++\begin{code}+repo_cmd :: [DarcsFlag] -> [String] -> IO ()+repo_cmd opts _ = let put_mode = if XMLOutput `elem` opts then showInfoXML else showInfoUsr+ in withRepository opts $- \repository -> showRepo (putInfo put_mode) repository+\end{code}++\begin{options}+--human-readable, --xml-output+\end{options}++By default, the \verb!show repo! displays output in human readable+form, but the \verb!--xml-output! option can be used to obtain+XML-formatted to facilitate regular parsing by external tools.++\begin{code}+-- Some convenience functions to output a labelled text string or an+-- XML tag + value (same API). If no value, output is suppressed+-- entirely. Borrow some help from Text.Html to perform XML output.++type ShowInfo = String -> String -> String++showInfoXML :: ShowInfo+showInfoXML t i = show $ tag (safeTag t) $ stringToHtml i++safeTag :: String -> String+safeTag [] = []+safeTag (' ':cs) = safeTag cs+safeTag ('#':cs) = "num_" ++ (safeTag cs)+safeTag (c:cs) = toLower c : safeTag cs++-- labelled strings: labels are right-aligned at 14 characters;+-- subsequent lines in multi-line output are indented accordingly.+showInfoUsr :: ShowInfo+showInfoUsr t i = (replicate (14 - length(t)) ' ') ++ t ++ ": " +++ (concat $ intersperse ('\n' : (replicate 16 ' ')) $ lines i) ++ "\n"++type PutInfo = String -> String -> IO ()+putInfo :: ShowInfo -> PutInfo+putInfo m t i = unless (null i) (putStr $ m t i)++-- Primary show-repo operation. Determines ordering of output for+-- sub-displays. The `out' argument is one of the above operations to+-- output a labelled text string or an XML tag and contained value.++showRepo :: RepoPatch p => PutInfo -> Repository p C(r u r) -> IO ()+showRepo out r@(Repo loc opts rf rt) = do+ when (XMLOutput `elem` opts) (putStr "<repository>\n")+ showRepoType out rt+ when (Verbose `elem` opts) (out "Show" $ show r)+ showRepoFormat out rf+ out "Root" loc+ showRepoAux out rt+ showRepoPrefs out+ unless (NoFiles `elem` opts) (numPatches r >>= (out "Num Patches" . show ))+ showRepoMOTD out r+ when (XMLOutput `elem` opts) (putStr "</repository>\n")++-- Most of the actual elements being displayed are part of the Show+-- class; that's fine for a Haskeller, but not for the common user, so+-- the routines below work to provide more human-readable information+-- regarding the repository elements.++showRepoType :: PutInfo -> RepoType p -> IO ()+showRepoType out (DarcsRepository _ _) = out "Type" "darcs"++showRepoFormat :: PutInfo -> RepoFormat -> IO ()+showRepoFormat out (RF rf) = out "Format" $+ concat $ intersperse ", " (map (concat . intersperse "|" . map BC.unpack) rf)++showRepoAux :: PutInfo -> RepoType p -> IO ()+showRepoAux out (DarcsRepository pris cs) =+ do out "Pristine" $ show pris+ out "Cache" $ concat $ intersperse ", " $ lines $ show cs+++showRepoPrefs :: PutInfo -> IO ()+showRepoPrefs out = do+ get_preflist "prefs" >>= mapM_ prefOut+ get_preflist "author" >>= out "Author" . unlines+ get_preflist "defaultrepo" >>= out "Default Remote" . unlines+ where prefOut = uncurry out . (\(p,v) -> (p++" Pref", (dropWhile isSpace v))) . break isSpace++showRepoMOTD :: RepoPatch p => PutInfo -> Repository p C(r u r) -> IO ()+showRepoMOTD out (Repo loc _ _ _) = get_motd loc >>= out "MOTD" . BC.unpack++-- Support routines to provide information used by the PutInfo operations above.++numPatches :: RepoPatch p => Repository p C(r u r) -> IO Int+numPatches r = read_repo r >>= (return . lengthRL . concatRL)++\end{code}
@@ -0,0 +1,80 @@+% Copyright (C) 2007 Florian Weimer+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\darcsCommand{show tags}+\begin{code}+module Darcs.Commands.ShowTags ( show_tags ) where+import Darcs.Arguments ( DarcsFlag(..), working_repo_dir )+import Darcs.Commands ( DarcsCommand(..), nodefaults )+import Darcs.Hopefully ( info )+import Darcs.Repository ( amInRepository, read_repo, withRepository, ($-) )+import Darcs.Patch.Info ( pi_tag )+import Darcs.Ordered ( mapRL, concatRL )+import System.IO ( stderr, hPutStrLn )+-- import Printer ( renderPS )++show_tags_description :: String+show_tags_description = "Show all tags in the repository."++show_tags_help :: String+show_tags_help =+ "The tags command writes a list of all tags in the repository to standard\n"+++ "output.\n" +++ "\n" +++ "Tab characters (ASCII character 9) in tag names are changed to spaces\n" +++ "for better interoperability with shell tools. A warning is printed if\n" +++ "this happens."++show_tags :: DarcsCommand+show_tags = DarcsCommand {+ command_name = "tags",+ command_help = show_tags_help,+ command_description = show_tags_description,+ command_extra_args = 0,+ command_extra_arg_help = [],+ command_command = tags_cmd,+ command_prereq = amInRepository,+ command_get_arg_possibilities = return [],+ command_argdefaults = nodefaults,+ command_advanced_options = [],+ command_basic_options = [working_repo_dir] }++tags_cmd :: [DarcsFlag] -> [String] -> IO ()+tags_cmd opts _ = withRepository opts $- \repository -> do+ patches <- read_repo repository+ sequence_ $ mapRL process $ concatRL patches+ where process hp =+ case pi_tag $ info hp of+ Just t -> do+ t' <- normalize t t False+ putStrLn t'+ Nothing -> return ()+ normalize :: String -> String -> Bool -> IO String+ normalize _ [] _ = return []+ normalize t (x : xs) flag =+ if x == '\t' then do+ if flag+ then return ()+ else hPutStrLn stderr+ ("warning: tag with TAB character: " ++ t)+ rest <- (normalize t xs True)+ return $ ' ' : rest+ else do+ rest <- (normalize t xs flag)+ return $ x : rest++\end{code}
@@ -0,0 +1,127 @@+% Copyright (C) 2003-2004 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\darcsCommand{tag}+\begin{code}+module Darcs.Commands.Tag ( tag ) where+import Control.Monad ( when )++import Darcs.Commands ( DarcsCommand(DarcsCommand, command_name, command_help,+ command_description, command_extra_args,+ command_extra_arg_help, command_command, command_prereq,+ command_get_arg_possibilities, command_argdefaults,+ command_advanced_options, command_basic_options),+ nodefaults )+import Darcs.Arguments ( nocompress, umask_option, patchname_option, author,+ checkpoint, pipe_interactive, ask_long_comment,+ working_repo_dir, get_author )+import Darcs.Hopefully ( n2pia )+import Darcs.Repository ( amInRepository, withRepoLock, ($-), read_repo,+ tentativelyAddPatch, finalizeRepositoryChanges, + )+import Darcs.Repository.Checkpoint ( write_recorded_checkpoint )+import Darcs.Patch ( infopatch, identity, adddeps )+import Darcs.Patch.Info ( patchinfo )+import Darcs.Patch.Depends ( get_tags_right )+import Darcs.Commands.Record ( get_date, get_log )+import Darcs.Ordered ( FL(..) )+import Darcs.Lock ( world_readable_temp )+import Darcs.Flags ( DarcsFlag(..) )+import System.IO ( hPutStr, stderr )++tag_description :: String+tag_description = "Name the current repository state for future reference."++tag_help :: String+tag_help =+ "The `darcs tag' command names the current repository state, so that it\n" +++ "can easily be referred to later. Every `important' state should be\n" +++ "tagged; in particular it is good practice to tag each stable release\n" +++ "with a number or codename. Advice on release numbering can be found\n" +++ "at http://producingoss.com/en/development-cycle.html.\n" +++ "\n" +++ "To reproduce the state of a repository `R' as at tag `t', use the\n" +++ "command `darcs get --tag t R'. The command `darcs show tags' lists\n" +++ "all tags in the current repository.\n" +++ "\n" +++ "Tagging also provides significant performance benefits: when Darcs\n" +++ "reaches a shared tag that depends on all antecedent patches, it can\n" +++ "simply stop processing.\n" +++ "\n" +++ "Like normal patches, a tag has a name, an author, a timestamp and an\n" +++ "optional long description, but it does not change the working tree.\n" +++ "A tag can have any name, but it is generally best to pick a naming\n" +++ "scheme and stick to it.\n" +++ "\n" +++ "The `darcs tag' command accepts the --pipe and --checkpoint options,\n" +++ "which behave as described in `darcs record' and `darcs optimize'\n" +++ "respectively.\n"++tag :: DarcsCommand+tag = DarcsCommand {command_name = "tag",+ command_help = tag_help,+ command_description = tag_description,+ command_extra_args = -1,+ command_extra_arg_help = ["[TAGNAME]"],+ command_command = tag_cmd,+ command_prereq = amInRepository,+ command_get_arg_possibilities = return [],+ command_argdefaults = nodefaults,+ command_advanced_options = [nocompress,umask_option],+ command_basic_options = [patchname_option, author,+ checkpoint,+ pipe_interactive,+ ask_long_comment,+ working_repo_dir]}++tag_cmd :: [DarcsFlag] -> [String] -> IO ()+tag_cmd opts args = withRepoLock opts $- \repository -> do+ date <- get_date opts+ the_author <- get_author opts+ deps <- get_tags_right `fmap` read_repo repository+ (name, long_comment) <- get_name_log opts args+ myinfo <- patchinfo date name the_author long_comment+ let mypatch = infopatch myinfo identity+ tentativelyAddPatch repository opts $ n2pia $ adddeps mypatch deps+ finalizeRepositoryChanges repository+ when (CheckPoint `elem` opts) $ write_recorded_checkpoint repository myinfo+ putStrLn $ "Finished tagging patch '"++name++"'"+ where get_name_log :: [DarcsFlag] -> [String] -> IO (String, [String])+ get_name_log o a = do let o2 = if null a then o else (add_patch_name o (unwords a))+ (name, comment, _) <- get_log o2 Nothing (world_readable_temp "darcs-tag") NilFL+ when (length name < 2) $ hPutStr stderr $+ "Do you really want to tag '"+ ++name++"'? If not type: darcs obliterate --last=1\n"+ return ("TAG " ++ name, comment)+ add_patch_name :: [DarcsFlag] -> String -> [DarcsFlag]+ add_patch_name o a| has_patch_name o = o+ | otherwise = [PatchName a] ++ o+ has_patch_name (PatchName _:_) = True+ has_patch_name (_:fs) = has_patch_name fs+ has_patch_name [] = False++-- This may be useful for developers, but users don't care about+-- internals:+--+-- A tagged version automatically depends on all patches in the+-- repository. This allows you to later reproduce precisely that+-- version. The tag does this by depending on all patches in the+-- repository, except for those which are depended upon by other tags+-- already in the repository. In the common case of a sequential+-- series of tags, this means that the tag depends on all patches+-- since the last tag, plus that tag itself.+\end{code}
@@ -0,0 +1,149 @@+% Copyright (C) 2002-2005 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\darcsCommand{trackdown}+\begin{code}+module Darcs.Commands.TrackDown ( trackdown ) where+import Prelude hiding ( init )+import System.Exit ( ExitCode(..) )+import System.Cmd ( system )+import System.IO ( hFlush, stdout )+import Control.Monad( when )++import Darcs.Commands ( DarcsCommand(..), nodefaults )+import Darcs.Arguments ( DarcsFlag(SetScriptsExecutable), working_repo_dir,+ set_scripts_executable )+import Darcs.Hopefully ( hopefully )+import Darcs.Repository ( amInRepository, read_repo, withRepoReadLock, ($-), withRecorded,+ setScriptsExecutable )+import Darcs.Ordered ( unsafeUnRL, concatRL )+import Darcs.Patch ( RepoPatch, Named, description, apply, invert )+import Printer ( putDocLn )+import Darcs.Test ( get_test )+import Darcs.Lock ( withTempDir )++trackdown_description :: String+trackdown_description = "Locate the most recent version lacking an error."++trackdown_help :: String+trackdown_help =+ "Trackdown tries to find the most recent version in the repository which\n"+++ "passes a test. Given no arguments, it uses the default repository test.\n"+++ "Given one argument, it treats it as a test command. Given two arguments,\n"+++ "the first is an initialization command with is run only once, and the\n"+++ "second is the test command.\n"++trackdown :: DarcsCommand+trackdown = DarcsCommand {command_name = "trackdown",+ command_help = trackdown_help,+ command_description = trackdown_description,+ command_extra_args = -1,+ command_extra_arg_help = ["[[INITIALIZATION]",+ "COMMAND]"],+ command_command = trackdown_cmd,+ command_prereq = amInRepository,+ command_get_arg_possibilities = return [],+ command_argdefaults = nodefaults,+ command_advanced_options = [set_scripts_executable],+ command_basic_options = [working_repo_dir]}++trackdown_cmd :: [DarcsFlag] -> [String] -> IO ()+trackdown_cmd opts args = withRepoReadLock opts $- \repository -> do+ patches <- read_repo repository+ (init,test) <- case args of+ [] ->+ do t <- get_test opts+ return (return ExitSuccess, t)+ [cmd] ->+ do putStrLn $ "Tracking down command:\n"++cmd+ return $ (return ExitSuccess, system cmd)+ [init,cmd] ->+ do putStrLn $ "Initializing with command:\n"++init+ putStrLn $ "Tracking down command:\n"++cmd+ return $ (system init, system cmd)+ _ -> fail "Trackdown expects zero to two arguments."+ withRecorded repository (withTempDir "trackingdown") $ \_ -> do+ when (SetScriptsExecutable `elem` opts) setScriptsExecutable+ init+ track_next opts test $ map (invert . hopefully) $ unsafeUnRL $ concatRL patches++track_next :: RepoPatch p => [DarcsFlag] -> (IO ExitCode) -> [Named p] -> IO ()+track_next opts test (p:ps) = do+ test_result <- test+ if test_result == ExitSuccess+ then putStrLn "Success!"+ else do apply opts p `catch` \e -> fail ("Bad patch:\n" ++ show e)+ putStrLn "Trying without the patch:"+ putDocLn $ description $ invert p+ hFlush stdout+ track_next opts test ps+track_next _ _ [] = putStrLn "Noone passed the test!"+\end{code}++Trackdown is helpful for locating when something was broken. It creates+a temporary directory with the latest repository content in it and cd to it.+First, and only once, it runs the initialization command if any,+for example+\begin{verbatim}+'autoconf; ./configure >/dev/null'+\end{verbatim}+Then it runs the test command, for example+\begin{verbatim}+'make && cd tests && sh /tmp/test.sh'+\end{verbatim}+While the test command exits with an error return code, darcs+``unapplies'' one patch from the version controlled files to retrieve+an earlier version, and repeats the test command. If the test command+finally succeeds, the name of the hunted down patch is found in the+output before the last test run.++FIXME: It is+still rather primitive. Currently it just goes back over the history in+reverse order trying each version. I'd like for it to explore different+patch combinations, to try to find the minimum number of patches that you+would need to obliterate in order to make the test succeed.++FIXME: I also would like to add an interface by which you can tell it which+patches it should consider not including. Without such a feature, the+following command:+\begin{verbatim}+% darcs trackdown 'make && false'+\end{verbatim}+would result in compiling every version in the repository--which is a+rather tedious prospect.++\subsubsection{Example usage}+If you want to find the last version of darcs that had a FIXME note in the+file Record.lhs, you could run+\begin{verbatim}+% darcs trackdown 'grep FIXME Record.lhs'+\end{verbatim}++To find the latest version that compiles, you can run+\begin{verbatim}+% darcs trackdown 'autoconf' './configure && make'+\end{verbatim}++Trackdown can also be used to see how other features of the code changed+with time. For example+\begin{verbatim}+% darcs trackdown 'autoconf; ./configure' \+ "make darcs > /dev/null && cd ~/darcs && time darcs check && false"+\end{verbatim}+would let you see how long `darcs check' takes to run on each previous+version of darcs that will actually compile. The ``\verb!&& false!''+ensures that trackdown keeps going.
@@ -0,0 +1,88 @@+% Copyright (C) 2008 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\darcsCommand{transfer-mode}+\begin{code}+{-# OPTIONS_GHC -cpp -fglasgow-exts #-}+{-# LANGUAGE CPP, PatternGuards #-}++-- The pragma above is only for pattern guards.+module Darcs.Commands.TransferMode ( transfer_mode ) where++import Prelude hiding ( catch )+import Control.Exception ( catch )+import System.IO ( stdout, hFlush )++import Darcs.Utils ( withCurrentDirectory, prettyException )+import Darcs.Commands ( DarcsCommand(..), nodefaults )+import Darcs.Arguments ( DarcsFlag, working_repo_dir )+import Darcs.Repository ( amInRepository )+import Progress ( setProgressMode )+import Darcs.Global ( darcsdir )++import qualified Data.ByteString as B (hPut, readFile, length, ByteString)++transfer_mode_description :: String+transfer_mode_description = "Internal command for efficient ssh transfers."++transfer_mode_help :: String+transfer_mode_help =+ "When pulling from or pushing to a remote repository over ssh, if both\n" +++ "the local and remote ends have Darcs 2, the `transfer-mode' command\n" +++ "will be invoked on the remote end. This allows Darcs to intelligently\n" +++ "transfer information over a single ssh connection.\n" +++ "\n" +++ "If either end runs Darcs 1, a separate ssh connection will be created\n" +++ "for each transfer. As well as being less efficient, this means users\n" +++ "who do not run ssh-agent will be prompted for the ssh password tens or\n" +++ "hundreds of times!\n"++transfer_mode :: DarcsCommand+transfer_mode = DarcsCommand {command_name = "transfer-mode",+ command_help = transfer_mode_help,+ command_description = transfer_mode_description,+ command_extra_args = 0,+ command_extra_arg_help = [],+ command_get_arg_possibilities = return [],+ command_command = transfer_mode_cmd,+ command_prereq = amInRepository,+ command_argdefaults = nodefaults,+ command_advanced_options = [],+ command_basic_options = [working_repo_dir]}++transfer_mode_cmd :: [DarcsFlag] -> [String] -> IO ()+transfer_mode_cmd _ _ = do setProgressMode False+ putStrLn "Hello user, I am darcs transfer mode"+ hFlush stdout+ withCurrentDirectory darcsdir $ transfer++transfer :: IO ()+transfer = do 'g':'e':'t':' ':fn <- getLine+ x <- readfile fn+ case x of+ Right c -> do putStrLn $ "got " ++ fn+ putStrLn $ show $ B.length c+ B.hPut stdout c+ hFlush stdout+ Left e -> do putStrLn $ "error " ++ fn+ putStrLn $ show e+ hFlush stdout+ transfer++readfile :: String -> IO (Either String B.ByteString)+readfile fn = (Right `fmap` B.readFile fn) `catch` (\e -> return $ Left (prettyException e))+\end{code}
@@ -0,0 +1,326 @@+% Copyright (C) 2002-2005 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\darcsCommand{unrecord}+\begin{code}+{-# OPTIONS_GHC -cpp #-}+{-# LANGUAGE CPP #-}++module Darcs.Commands.Unrecord ( unrecord, unpull, obliterate, get_last_patches ) where+import Control.Monad ( when )+import System.Exit ( exitWith, ExitCode( ExitSuccess ) )++import Darcs.SlurpDirectory ( wait_a_moment )+import Darcs.Hopefully ( hopefully )+import Darcs.Commands ( DarcsCommand(..), nodefaults, loggers, command_alias )+import Darcs.Arguments ( DarcsFlag( Verbose ),+ working_repo_dir, nocompress, definePatches,+ match_several_or_last, deps_sel,+ ignoretimes,+ all_interactive, umask_option, summary+ )+import Darcs.Match ( first_match, match_first_patchset, match_a_patchread )+import Darcs.Repository ( PatchSet, PatchInfoAnd, withGutsOf,+ withRepoLock, ($-),+ tentativelyRemovePatches, finalizeRepositoryChanges,+ tentativelyAddToPending,+ applyToWorking,+ get_unrecorded, read_repo, amInRepository,+ sync_repo,+ )+import Darcs.Patch ( Patchy, RepoPatch, invert, commutex, effect )+import Darcs.Ordered ( RL(..), (:<)(..), (:>)(..), (:\/:)(..), (+<+),+ mapFL_FL, nullFL,+ concatRL, reverseRL, mapRL )+import Darcs.Patch.Depends ( get_common_and_uncommon )+import Darcs.SelectChanges ( with_selected_last_changes_reversed )+import Progress ( debugMessage )+import Darcs.Sealed ( Sealed(..), FlippedSeal(..), mapFlipped )+import Darcs.Gorsvet( invalidateIndex )+#include "gadts.h"++unrecord_description :: String+unrecord_description =+ "Remove recorded patches without changing the working copy."+\end{code}++Unrecord can be thought of as undo-record.+If a record is followed by an unrecord, everything looks like before+the record; all the previously unrecorded changes are back, and can be+recorded again in a new patch. The unrecorded patch however is actually+removed from your repository, so there is no way to record it again to get+it back.\footnote{The patch file itself is not actually deleted, but its+context is lost, so it cannot be reliably read---your only choice would be+to go in by hand and read its contents.}.++If you want to remove+the changes from the working copy too (where they otherwise will show+up as unrecorded changes again), you'll also need to \verb!darcs revert!.+To do unrecord and revert in one go, you can use \verb!darcs obliterate!.++If you don't revert after unrecording, then the changes made by the+unrecorded patches are left in your working tree. If these patches are+actually from another repository, interaction (either pushes or pulls) with+that repository may be massively slowed down, as darcs tries to cope with+the fact that you appear to have made a large number of changes that+conflict with those present in the other repository. So if you really want+to undo the result of a \emph{pull} operation, use obliterate! Unrecord is+primarily intended for when you record a patch, realize it needs just one+more change, but would rather not have a separate patch for just that one+change.++\newcommand{\pullwarning}[1]{+\textbf{WARNING:} #1 should not be run when there is a possibility+that another user may be pulling from the same repository. Attempting to do so+may cause repository corruption.}++\pullwarning{Unrecord}++\begin{options}+--from-match, --from-patch, --from-tag, --last+\end{options}++Usually you only want to unrecord the latest changes,+and almost never would you want to unrecord changes before a tag---you+would have to have unrecorded the tag as well to do that.+Therefore, and for efficiency, darcs only prompts you for the latest patches,+after some optimal tag.++If you do want to unrecord more patches in one go,+there are the \verb!--from! and \verb!--last! options+to set the earliest patch selectable to unrecord.++\begin{options}+--matches, --patches, --tags, --no-deps+\end{options}++The \verb!--patches!, \verb!--matches!, \verb!--tags!, and \verb!--no-deps!+options can be used to select which patches to unrecord, as described in+subsection~\ref{selecting}.++With these options you can specify+what patch or patches to be prompted for by unrecord.+This is especially useful when you want to unrecord patches with dependencies,+since all the dependent patches (but no others) will be included in the choices.+Or if you use \verb!--no-deps! you won't be asked about patches that can't be+unrecorded due to depending patches.++Selecting patches can be slow, so darcs cuts the search at the last+optimized tag. Use the \verb!--from! or \verb!--last! options to search+more or fewer patches.++\begin{code}+unrecord_help :: String+unrecord_help =+ "Unrecord does the opposite of record in that it makes the changes from\n"+++ "patches active changes again which you may record or revert later. The\n"+++ "working copy itself will not change.\n"+++ "Beware that you should not use this command if you are going to\n"+++ "re-record the changes in any way and there is a possibility that\n"+++ "another user may have already pulled the patch.\n"++unrecord :: DarcsCommand+unrecord = DarcsCommand {command_name = "unrecord",+ command_help = unrecord_help,+ command_description = unrecord_description,+ command_extra_args = 0,+ command_extra_arg_help = [],+ command_command = unrecord_cmd,+ command_prereq = amInRepository,+ command_get_arg_possibilities = return [],+ command_argdefaults = nodefaults,+ command_advanced_options = [nocompress,umask_option],+ command_basic_options = [match_several_or_last,+ deps_sel,+ all_interactive,+ working_repo_dir]}++unrecord_cmd :: [DarcsFlag] -> [String] -> IO ()+unrecord_cmd opts _ = withRepoLock opts $- \repository -> do+ let (logMessage,_,_) = loggers opts+ allpatches <- read_repo repository+ FlippedSeal patches <- return $ if first_match opts+ then get_last_patches opts allpatches+ else matchingHead opts allpatches+ with_selected_last_changes_reversed "unrecord" opts+ (reverseRL patches) $+ \ (_ :> to_unrecord) -> do+ when (nullFL to_unrecord) $ do logMessage "No patches selected!"+ exitWith ExitSuccess+ when (Verbose `elem` opts) $+ logMessage "About to write out (potentially) modified patches..."+ definePatches to_unrecord+ invalidateIndex repository+ withGutsOf repository $ do tentativelyRemovePatches repository opts $+ mapFL_FL hopefully to_unrecord+ finalizeRepositoryChanges repository+ sync_repo repository+ logMessage "Finished unrecording."++get_last_patches :: RepoPatch p => [DarcsFlag] -> PatchSet p C(r)+ -> FlippedSeal (RL (PatchInfoAnd p)) C(r)+get_last_patches opts ps =+ case match_first_patchset opts ps of+ Sealed p1s -> case get_common_and_uncommon (ps,p1s) of+ (_,us :\/: _) -> FlippedSeal $ concatRL us++unpull_description :: String+unpull_description =+ "Opposite of pull; unsafe if patch is not in remote repository."++unpull_help :: String+unpull_help =+ "Unpull completely removes recorded patches from your local repository.\n"+++ "The changes will be undone in your working copy and the patches will not be\n"+++ "shown in your changes list anymore.\n"+++ "Beware that if the patches are not still present in another repository you\n"+++ "will lose precious code by unpulling!\n"++unpull :: DarcsCommand+unpull = (command_alias "unpull" obliterate)+ {command_help = unpull_help,+ command_description = unpull_description,+ command_command = unpull_cmd}++unpull_cmd :: [DarcsFlag] -> [String] -> IO ()+unpull_cmd = generic_obliterate_cmd "unpull"++\end{code}+\darcsCommand{obliterate}+\begin{code}++obliterate_description :: String+obliterate_description =+ "Delete selected patches from the repository. (UNSAFE!)"++obliterate_help :: String+obliterate_help =+ "Obliterate completely removes recorded patches from your local repository.\n"+++ "The changes will be undone in your working copy and the patches will not be\n"+++ "shown in your changes list anymore.\n"+++ "Beware that you can lose precious code by obliterating!\n"++\end{code}+Obliterate deletes a patch from the repository \emph{and} removes those+changes from the working directory. It is therefore a \emph{very+dangerous} command. When there are no local changes, obliterate is+equivalent to an unrecord followed by a revert, except that revert can be+unreverted. In the case of tags, obliterate removes the tag itself, not+any other patches.++Note that unpull was the old name for obliterate. Unpull is still an+hidden alias for obliterate.++\pullwarning{Obliterate}++\begin{options}+--from-match, --from-patch, --from-tag, --last+\end{options}++Usually you only want to obliterate the latest changes, and almost never would+you want to obliterate changes before a tag---you would have to have obliterated+the tag as well to do that. Therefore, and for efficiency, darcs only+prompts you for the latest patches, after some optimal tag.++If you do want to obliterate more patches in one go, there are the+\verb!--from! and \verb!--last! options to set the earliest patch+selectable to obliterate.++\begin{options}+--matches, --patches, --tags, --no-deps+\end{options}++The \verb!--patches!, \verb!--matches!, \verb!--tags!, and \verb!--no-deps!+options can be used to select which patches to obliterate, as described in+subsection~\ref{selecting}.++With these options you can specify what patch or patches to be prompted for+by obliterate. This is especially useful when you want to obliterate patches with+dependencies, since all the dependent patches (but no others) will be+included in the choices. Or if you use \verb!--no-deps! you won't be asked+about patches that can't be obliterated due to depending patches.++Selecting patches can be slow, so darcs cuts the search at the last+optimized tag. Use the \verb!--from! or \verb!--last! options to search+more or fewer patches.++\begin{code}+obliterate :: DarcsCommand+obliterate = DarcsCommand {command_name = "obliterate",+ command_help = obliterate_help,+ command_description = obliterate_description,+ command_extra_args = 0,+ command_extra_arg_help = [],+ command_command = obliterate_cmd,+ command_prereq = amInRepository,+ command_get_arg_possibilities = return [],+ command_argdefaults = nodefaults,+ command_advanced_options = [nocompress,ignoretimes,umask_option],+ command_basic_options = [match_several_or_last,+ deps_sel,+ all_interactive,+ working_repo_dir,+ summary]}+obliterate_cmd :: [DarcsFlag] -> [String] -> IO ()+obliterate_cmd = generic_obliterate_cmd "obliterate"++-- | generic_obliterate_cmd is the function that executes the "obliterate" and+-- "unpull" commands.+generic_obliterate_cmd :: String -- ^ The name under which the command is invoked (@unpull@ or @obliterate@)+ -> [DarcsFlag] -- ^ The flags given on the command line+ -> [String] -- ^ Files given on the command line (unused)+ -> IO ()+generic_obliterate_cmd cmdname opts _ = withRepoLock opts $- \repository -> do+ let (logMessage,_,_) = loggers opts+ pend <- get_unrecorded repository+ allpatches <- read_repo repository+ FlippedSeal patches <- return $ if first_match opts+ then get_last_patches opts allpatches+ else matchingHead opts allpatches+ with_selected_last_changes_reversed cmdname opts+ (reverseRL patches) $+ \ (_ :> ps) ->+ case commutex (pend :< effect ps) of+ Nothing -> fail $ "Can't "++ cmdname +++ " patch without reverting some unrecorded change."+ Just (p_after_pending:<_) -> do+ when (nullFL ps) $ do logMessage "No patches selected!"+ exitWith ExitSuccess+ definePatches ps+ invalidateIndex repository+ withGutsOf repository $+ do tentativelyRemovePatches repository opts (mapFL_FL hopefully ps)+ tentativelyAddToPending repository opts $ invert $ effect ps+ finalizeRepositoryChanges repository+ debugMessage "Waiting a bit for timestamps to differ..."+ wait_a_moment+ debugMessage "Applying patches to working directory..."+ applyToWorking repository opts (invert p_after_pending) `catch` \e ->+ fail ("Couldn't undo patch in working dir.\n" ++ show e)+ sync_repo repository+ logMessage $ "Finished " ++ present_participle cmdname ++ "."++matchingHead :: Patchy p => [DarcsFlag] -> PatchSet p C(r) -> FlippedSeal (RL (PatchInfoAnd p)) C(r)+matchingHead opts (x:<:_) | or (mapRL (match_a_patchread opts) x) = FlippedSeal x+matchingHead opts (x:<:xs) = (x +<+) `mapFlipped` matchingHead opts xs+matchingHead _ NilRL = FlippedSeal NilRL++present_participle :: String -> String+present_participle v | last v == 'e' = init v ++ "ing"+ | otherwise = v ++ "ing"+\end{code}+
@@ -0,0 +1,136 @@+% Copyright (C) 2003-2005 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\darcsCommand{unrevert}+\begin{code}+{-# OPTIONS_GHC -cpp #-}+{-# LANGUAGE CPP #-}++#include "gadts.h"++module Darcs.Commands.Unrevert ( unrevert, write_unrevert ) where+import System.Exit ( ExitCode(..), exitWith )++import Darcs.Commands ( DarcsCommand(..), nodefaults )+import Darcs.Arguments ( DarcsFlag( Unified, MarkConflicts ),+ ignoretimes, working_repo_dir,+ all_interactive, umask_option,+ )+import Darcs.Repository ( SealedPatchSet, Repository, withRepoLock, ($-),+ unrevertUrl, considerMergeToWorking,+ tentativelyAddToPending, finalizeRepositoryChanges,+ sync_repo, get_unrecorded,+ read_repo, amInRepository,+ slurp_recorded,+ applyToWorking )+import Darcs.Patch ( RepoPatch, Prim, commutex, namepatch, fromPrims )+import Darcs.Ordered ( RL(..), FL(..), (:<)(..), (:>)(..), (:\/:)(..), reverseRL,+ (+>+) )+import Darcs.SelectChanges ( with_selected_changes_to_files' )+import Darcs.SlurpDirectory ( Slurpy )+import qualified Data.ByteString as B+import Darcs.Lock ( writeDocBinFile, removeFileMayNotExist )+import Darcs.Patch.Depends ( get_common_and_uncommon )+import Darcs.Utils ( askUser, catchall )+import Darcs.Patch.Bundle ( scan_bundle, make_bundle )+import IsoDate ( getIsoDateTime )+import Darcs.SignalHandler ( withSignalsBlocked )+import Progress ( debugMessage )+import Darcs.Sealed ( Sealed(Sealed) )+#include "impossible.h"++unrevert_description :: String+unrevert_description =+ "Undo the last revert (may fail if changes after the revert)."++unrevert_help :: String+unrevert_help =+ "Unrevert is a rescue command in case you accidentally reverted\n" +++ "something you wanted to keep (for example, typing `darcs rev -a'\n" +++ "instead of `darcs rec -a').\n" +++ "\n" +++ "This command may fail if the repository has changed since the revert\n" +++ "took place. Darcs will ask for confirmation before executing an\n" +++ "interactive command that will DEFINITELY prevent unreversion.\n"++unrevert :: DarcsCommand+unrevert = DarcsCommand {command_name = "unrevert",+ command_help = unrevert_help,+ command_description = unrevert_description,+ command_extra_args = 0,+ command_extra_arg_help = [],+ command_command = unrevert_cmd,+ command_prereq = amInRepository,+ command_get_arg_possibilities = return [],+ command_argdefaults = nodefaults,+ command_advanced_options = [umask_option],+ command_basic_options = [ignoretimes,+ all_interactive,+ working_repo_dir]}++unrevert_cmd :: [DarcsFlag] -> [String] -> IO ()+unrevert_cmd opts [] = withRepoLock opts $- \repository -> do+ us <- read_repo repository+ Sealed them <- unrevert_patch_bundle repository+ rec <- slurp_recorded repository+ unrec <- get_unrecorded repository+ case get_common_and_uncommon (us, them) of+ (_, (h_us:<:NilRL) :\/: (h_them:<:NilRL)) -> do+ Sealed pw <- considerMergeToWorking repository "pull" (MarkConflicts:opts)+ (reverseRL h_us) (reverseRL h_them)+ with_selected_changes_to_files' "unrevert" opts [] pw $+ \ (p :> skipped) -> do+ tentativelyAddToPending repository opts p+ withSignalsBlocked $+ do finalizeRepositoryChanges repository+ applyToWorking repository opts p `catch` \e ->+ fail ("Error applying unrevert to working directory...\n"+ ++ show e)+ debugMessage "I'm about to write_unrevert."+ write_unrevert repository skipped rec (unrec+>+p)+ sync_repo repository+ debugMessage "Finished unreverting."+ _ -> impossible+unrevert_cmd _ _ = impossible++write_unrevert :: RepoPatch p => Repository p C(r u t) -> FL Prim C(x y) -> Slurpy -> FL Prim C(r x) -> IO ()+write_unrevert repository NilFL _ _ = removeFileMayNotExist $ unrevertUrl repository+write_unrevert repository ps rec pend = do+ case commutex (ps :< pend) of+ Nothing -> do really <- askUser "You will not be able to unrevert this operation! Proceed? "+ case really of ('y':_) -> return ()+ _ -> exitWith $ ExitSuccess+ write_unrevert repository NilFL rec pend+ Just (_ :< p') -> do+ rep <- read_repo repository+ case get_common_and_uncommon (rep,rep) of+ (common,_ :\/: _) -> do+ date <- getIsoDateTime+ np <- namepatch date "unrevert" "anon" [] (fromRepoPrims repository p')+ writeDocBinFile (unrevertUrl repository) $+ make_bundle [Unified] rec common (np :>: NilFL)+ where fromRepoPrims :: RepoPatch p => Repository p C(r u t) -> FL Prim C(r y) -> p C(r y)+ fromRepoPrims _ xs = fromPrims xs++unrevert_patch_bundle :: RepoPatch p => Repository p C(r u t) -> IO (SealedPatchSet p)+unrevert_patch_bundle repository = do+ pf <- B.readFile (unrevertUrl repository)+ `catchall` fail "There's nothing to unrevert!"+ case scan_bundle pf of+ Right ps -> return ps+ Left err -> fail $ "Couldn't parse unrevert patch:\n" ++ err+\end{code}
@@ -0,0 +1,160 @@+% Copyright (C) 2002-2004 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\darcsCommand{whatsnew}+\begin{code}+{-# OPTIONS_GHC -cpp #-}+{-# LANGUAGE CPP #-}++#include "gadts.h"++module Darcs.Commands.WhatsNew ( whatsnew ) where+import System.Exit ( ExitCode(..), exitWith )+import Data.List ( sort )+import Control.Monad ( when )++import Darcs.Commands ( DarcsCommand(..), nodefaults )+import Darcs.Arguments ( DarcsFlag(..), working_repo_dir, lookforadds,+ ignoretimes, noskip_boring,+ unified, summary, no_cache,+ areFileArgs, fixSubPaths,+ list_registered_files,+ )+import Darcs.RepoPath ( SubPath, sp2fn )++import Darcs.Repository ( Repository, withRepository, ($-), slurp_recorded,+ get_unrecorded_no_look_for_adds,+ get_unrecorded_in_files, amInRepository )+import Darcs.Repository.Prefs ( filetype_function )+import Darcs.Diff ( unsafeDiff )+import Darcs.Patch ( RepoPatch, Prim, summarize, apply_to_slurpy, is_hunk )+import Darcs.Patch.Permutations ( partitionRL )+import Darcs.Patch.Real ( RealPatch, prim2real )+import Darcs.Patch.FileName ( fn2fp )+import Darcs.PrintPatch ( printPatch, contextualPrintPatch )+import Darcs.Ordered ( FL(..), mapFL_FL, reverseRL, reverseFL, (:>)(..), nullFL )++import Darcs.Gorsvet( unrecordedChanges, restrictBoring, readRecordedAndPending )+import Storage.Hashed.Monad( virtualTreeIO, exists )+import Storage.Hashed( readPlainTree )+import Storage.Hashed( floatPath )++import Printer ( putDocLn, renderString, vcat, text )+#include "impossible.h"++whatsnew_description :: String+whatsnew_description = "List unrecorded changes in the working tree."++whatsnew_help :: String+whatsnew_help =+ "The `darcs whatsnew' command lists unrecorded changes to the working\n" +++ "tree. If you specify a set of files and directories, only unrecorded\n" +++ "changes to those files and directories are listed.\n" +++ "\n" +++ "With the --summary option, the changes are condensed to one line per\n" +++ "file, with mnemonics to indicate the nature and extent of the change.\n" +++ "The --look-for-adds option causes candidates for `darcs add' to be\n" +++ "included in the summary output.\n" +++ "\n" +++ "By default, `darcs whatsnew' uses Darcs' internal format for changes.\n" +++ "To see some context (unchanged lines) around each change, use the\n" +++ "--unified option. To view changes in conventional `diff' format, use\n" +++ "the `darcs diff' comand; but note that `darcs whatsnew' is faster.\n" +++ "\n" +++ "This command exits unsuccessfully (returns a non-zero exit status) if\n" +++ "there are no unrecorded changes.\n"++whatsnew :: DarcsCommand+whatsnew = DarcsCommand {command_name = "whatsnew",+ command_help = whatsnew_help,+ command_description = whatsnew_description,+ command_extra_args = -1,+ command_extra_arg_help = ["[FILE or DIRECTORY]..."],+ command_command = whatsnew_cmd,+ command_prereq = amInRepository,+ command_get_arg_possibilities = list_registered_files,+ command_argdefaults = nodefaults,+ command_advanced_options = [ignoretimes, noskip_boring, no_cache],+ command_basic_options = [summary, unified,+ lookforadds,+ working_repo_dir]}++announce_files :: (RepoPatch p) => Repository p C(r u t) -> [SubPath] -> IO ()+announce_files repo files =+ when (areFileArgs files) $ do+ nonboring <- restrictBoring+ working <- nonboring `fmap` readPlainTree "."+ pristine <- readRecordedAndPending repo+ let paths = map (fn2fp . sp2fn) files+ check = virtualTreeIO (mapM exists $ map floatPath paths)+ (in_working, _) <- check working+ (in_pending, _) <- check pristine+ mapM_ maybe_warn $ zip3 paths in_working in_pending+ putStrLn $ "What's new in "++unwords (map show files)++":\n"+ where maybe_warn (file, False, False) =+ putStrLn $ "WARNING: File '"++file++"' does not exist!"+ maybe_warn (file, True, False) =+ putStrLn $ "WARNING: File '" ++ file ++ "' not in repository!"+ maybe_warn _ = return ()++whatsnew_cmd :: [DarcsFlag] -> [String] -> IO ()+whatsnew_cmd opts' args + | LookForAdds `elem` opts' && NoSummary `notElem` opts' =+ -- add Summary to the opts since 'darcs whatsnew --look-for-adds'+ -- implies summary+ withRepository (Summary:opts') $- \repository -> do+ files <- fixSubPaths opts' args+ announce_files repository files+ all_changes <- get_unrecorded_in_files repository (map sp2fn files)+ chold <- get_unrecorded_no_look_for_adds repository (map sp2fn files)+ s <- slurp_recorded repository+ ftf <- filetype_function+ cho_adds :> _ <- return $ partitionRL is_hunk $ reverseFL chold+ cha :> _ <- return $ partitionRL is_hunk $ reverseFL all_changes+ let chn = unsafeDiff [LookForAdds,Summary] ftf+ (fromJust $ apply_to_slurpy (reverseRL cho_adds) s)+ (fromJust $ apply_to_slurpy (reverseRL cha) s)+ exitOnNoChanges (chn, chold)+ putDocLn $ summarize chold+ printSummary chn+ where lower_as x = vcat $ map (text . l_as) $ lines x+ l_as ('A':x) = 'a':x+ l_as x = x+ exitOnNoChanges :: (FL Prim C(x y), FL p C(u v)) -> IO ()+ exitOnNoChanges (NilFL, NilFL) = do putStrLn "No changes!"+ exitWith $ ExitFailure 1+ exitOnNoChanges _ = return ()+ printSummary :: FL Prim C(x y) -> IO ()+ printSummary NilFL = return ()+ printSummary new = putDocLn $ lower_as $ renderString $ summarize new++whatsnew_cmd opts args+ | otherwise =+ withRepository opts $- \repository -> do+ files <- sort `fmap` fixSubPaths opts args+ announce_files repository files+ changes <- unrecordedChanges opts repository files+ when (nullFL changes) $ putStrLn "No changes!" >> (exitWith $ ExitFailure 1)+ printSummary repository $ mapFL_FL prim2real changes+ where printSummary :: RepoPatch p => Repository p C(r u t) -> FL RealPatch C(r y) -> IO ()+ printSummary _ NilFL = do putStrLn "No changes!"+ exitWith $ ExitFailure 1+ printSummary r ch | Summary `elem` opts = putDocLn $ summarize ch+ | Unified `elem` opts = do s <- slurp_recorded r+ contextualPrintPatch s ch+ | otherwise = printPatch ch+\end{code}
@@ -0,0 +1,112 @@+-- Copyright (C) 2006 Tommy Pettersson <ptp@lysator.liu.se>+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.++{-# OPTIONS_GHC -cpp #-}+{-# LANGUAGE CPP #-}++#include "gadts.h"++module Darcs.CommandsAux ( check_paths, malicious_patches, has_malicious_path,+ ) where+import Darcs.Flags ( DarcsFlag( RestrictPaths, DontRestrictPaths ) )+import Darcs.Patch ( Patchy, list_touched_files )+import Darcs.Ordered ( FL, mapFL )+import Darcs.Sealed ( Sealed2(..), unseal2 )+import Darcs.Global ( darcsdir )+import Data.List ( intersect )+import System.FilePath ( splitDirectories )++-- * File paths+{-+ Darcs will operate on files and directories with the invoking user's+ privileges. The paths for these files and directories are stored in+ patches, which darcs receives in various ways. Even though darcs will not+ create patches with "unexpected" file paths, there are no such guarantees+ for received patches. A spoofed patch could inflict changes on any file+ or directory which the invoking user is privileged to modify.++ There is no one single "apply" function that can check paths, so each+ command is responsible for not applying patches without first checking+ them with one of these function when appropriate.+-}++{- |+ A convenience function to call from all darcs command functions before+ applying any patches. It checks for malicious paths in patches, and+ prints an error message and fails if it finds one.+-}+check_paths :: Patchy p => [DarcsFlag] -> FL p C(x y) -> IO ()+check_paths opts patches+ = if check_is_on && or (mapFL has_malicious_path patches)+ then fail $ unlines $ ["Malicious path in patch:"] +++ (map (\s -> " " ++ s) $ concat $ mapFL malicious_paths patches) +++ ["", "If you are sure this is ok then you can run again with the --dont-restrict-paths option."]+ -- TODO: print patch(es)+ -- NOTE: should use safe Doc printer, this can be evil chars+ else return ()+ where+ check_is_on = DontRestrictPaths `notElem` opts ||+ RestrictPaths `elem` opts++-- | Filter out patches that contains some malicious file path+malicious_patches :: Patchy p => [Sealed2 p] -> [Sealed2 p]+malicious_patches to_check = filter (unseal2 has_malicious_path) to_check++has_malicious_path :: Patchy p => p C(x y) -> Bool+has_malicious_path patch =+ case malicious_paths patch of+ [] -> False+ _ -> True++malicious_paths :: Patchy p => p C(x y) -> [String]+malicious_paths patch =+ let paths = list_touched_files patch in+ filter is_malicious_path paths++{-|+ What is a malicious path?++ A spoofed path is a malicious path.++ 1. Darcs only creates explicitly relative paths (beginning with @\".\/\"@),+ so any not explicitly relative path is surely spoofed.++ 2. Darcs normalizes paths so they never contain @\"\/..\/\"@, so paths with+ @\"\/..\/\"@ are surely spoofed.++ A path to a darcs repository's meta data can modify \"trusted\" patches or+ change safety defaults in that repository, so we check for paths+ containing @\"\/_darcs\/\"@ which is the entry to darcs meta data.++ To do?++ * How about get repositories?++ * Would it be worth adding a --semi-safe-paths option for allowing+ changes to certain preference files (_darcs\/prefs\/) in sub+ repositories'?+-}+is_malicious_path :: String -> Bool+is_malicious_path fp =+ not (is_explicitly_relative fp) ||+ splitDirectories fp `contains_any` [ "..", darcsdir ]+ where+ contains_any a b = not . null $ intersect a b++is_explicitly_relative :: String -> Bool+is_explicitly_relative ('.':'/':_) = True -- begins with "./"+is_explicitly_relative _ = False
@@ -0,0 +1,123 @@+{-# LANGUAGE CPP, ForeignFunctionInterface #-}++module Darcs.Compat (stdout_is_a_pipe, mk_stdout_temp, canonFilename,+ maybeRelink, atomic_create, sloppy_atomic_create) where++import Prelude hiding ( catch )++import Darcs.Utils ( withCurrentDirectory )+#ifdef WIN32+import Darcs.Utils ( showHexLen )+import Data.Bits ( (.&.) )+import System.Random ( randomIO )+#else+import Foreign.C.String ( peekCString )+#endif++import Control.Monad ( unless )+import Foreign.C.Types ( CInt )+import Foreign.C.String ( CString, withCString )+import Foreign.C.Error ( throwErrno, eEXIST, getErrno )+import System.Directory ( getCurrentDirectory )+import System.IO ( hFlush, stdout, stderr, hSetBuffering,+ BufferMode(NoBuffering) )+import System.IO.Error ( mkIOError, alreadyExistsErrorType )+import System.Posix.Files ( stdFileMode )+import System.Posix.IO ( openFd, closeFd, stdOutput, stdError, + dupTo, defaultFileFlags, exclusive,+ OpenMode(WriteOnly) )+import System.Posix.Types ( Fd(..) )++import Darcs.SignalHandler ( stdout_is_a_pipe )++canonFilename :: FilePath -> IO FilePath+canonFilename f@(_:':':_) = return f -- absolute windows paths+canonFilename f@('/':_) = return f+canonFilename ('.':'/':f) = do cd <- getCurrentDirectory+ return $ cd ++ "/" ++ f+canonFilename f = case reverse $ dropWhile (/='/') $ reverse f of+ "" -> fmap (++('/':f)) getCurrentDirectory+ rd -> withCurrentDirectory rd $+ do fd <- getCurrentDirectory+ return $ fd ++ "/" ++ simplefilename+ where+ simplefilename = reverse $ takeWhile (/='/') $ reverse f++#ifdef WIN32+mkstemp_core :: FilePath -> IO (Fd, String)+mkstemp_core fp+ = do r <- randomIO+ let fp' = fp ++ (showHexLen 6 (r .&. 0xFFFFFF :: Int))+ fd <- openFd fp' WriteOnly (Just stdFileMode) flags+ return (fd, fp')+ where flags = defaultFileFlags { exclusive = True }+#else+mkstemp_core :: String -> IO (Fd, String)+mkstemp_core str = withCString (str++"XXXXXX") $+ \cstr -> do fd <- c_mkstemp cstr+ if fd < 0+ then throwErrno $ "Failed to create temporary file "++str+ else do str' <- peekCString cstr+ fname <- canonFilename str'+ return (Fd fd, fname)++foreign import ccall unsafe "static stdlib.h mkstemp"+ c_mkstemp :: CString -> IO CInt+#endif++mk_stdout_temp :: String -> IO String+mk_stdout_temp str = do (fd, fn) <- mkstemp_core str+ hFlush stdout+ hFlush stderr+ dupTo fd stdOutput+ dupTo fd stdError+ hFlush stdout+ hFlush stderr+ hSetBuffering stdout NoBuffering+ hSetBuffering stderr NoBuffering+ return fn++++foreign import ccall unsafe "maybe_relink.h maybe_relink" maybe_relink+ :: CString -> CString -> CInt -> IO CInt++-- Checks whether src and dst are identical. If so, makes dst into a+-- link to src. Returns True if dst is a link to src (either because+-- we linked it or it already was). Safe against changes to src if+-- they are not in place, but not to dst.+maybeRelink :: String -> String -> IO Bool+maybeRelink src dst =+ withCString src $ \csrc ->+ withCString dst $ \cdst ->+ do rc <- maybe_relink csrc cdst 1+ (case rc of+ 0 -> return True+ 1 -> return True+ -1 -> throwErrno ("Relinking " ++ dst)+ -2 -> return False+ -3 -> do putStrLn ("Relinking: race condition avoided on file " +++ dst)+ return False+ _ -> fail ("Unexpected situation when relinking " ++ dst))++sloppy_atomic_create :: FilePath -> IO ()+sloppy_atomic_create fp+ = do fd <- openFd fp WriteOnly (Just stdFileMode) flags+ closeFd fd+ where flags = defaultFileFlags { exclusive = True }++atomic_create :: FilePath -> IO ()+atomic_create fp = withCString fp $ \cstr -> do+ rc <- c_atomic_create cstr+ unless (rc >= 0) $+ do errno <- getErrno+ pwd <- getCurrentDirectory+ if errno == eEXIST+ then ioError $ mkIOError alreadyExistsErrorType+ ("atomic_create in "++pwd)+ Nothing (Just fp)+ else throwErrno $ "atomic_create "++fp++" in "++pwd++foreign import ccall unsafe "atomic_create.h atomic_create" c_atomic_create+ :: CString -> IO CInt
@@ -0,0 +1,375 @@+-- Copyright (C) 2002-2003 David Roundy+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.++{-# OPTIONS_GHC -cpp -fglasgow-exts #-}+{-# LANGUAGE CPP #-}++#include "gadts.h"++module Darcs.Diff ( unsafeDiffAtPaths, unsafeDiff, sync, cmp+#ifndef GADT_WITNESSES+ , diff_files+#endif+ ) where++import System.Posix+ ( setFileTimes )+import System.IO ( IOMode(ReadMode), hFileSize, hClose )+import System.Directory ( doesDirectoryExist, doesFileExist,+ getDirectoryContents,+ )+import Control.Monad ( when )+import Data.List ( sort+#ifndef GADT_WITNESSES+ , intersperse, isPrefixOf+#endif+ )+#ifndef GADT_WITNESSES+import Data.Maybe ( catMaybes )+#endif++#ifndef GADT_WITNESSES+import ByteStringUtils ( is_funky, linesPS)+import qualified Data.ByteString.Char8 as BC (last)+import qualified Data.ByteString as B (null, empty, take, ByteString)+#endif+import qualified Data.ByteString as B (hGet, length)++import Darcs.SlurpDirectory ( Slurpy, slurp_name, is_dir, is_file,+#ifndef GADT_WITNESSES+ get_slurp,+#endif+ get_dircontents, get_filecontents,+ get_mtime, get_length,+ undefined_time+#ifndef GADT_WITNESSES+ , FileContents, undefined_size+#endif+ )+#ifndef GADT_WITNESSES+import System.FilePath.Posix ( (</>) )+#endif+import Darcs.Patch ( Prim+#ifndef GADT_WITNESSES+ , hunk, canonize, rmfile, rmdir+ , addfile, adddir+ , binary, invert+#endif+ )+#ifndef GADT_WITNESSES+import Darcs.Patch.FileName( fp2fn, breakup )+#endif+import System.IO ( openBinaryFile )+import Darcs.Repository.Prefs ( FileType(..) )+import Darcs.Flags ( DarcsFlag(..) )+import Darcs.Utils ( catchall )+import Darcs.Ordered ( FL(..)+#ifndef GADT_WITNESSES+ , (+>+)+#endif+ )+#ifndef GADT_WITNESSES+#include "impossible.h"+#endif++-- | The unsafeDiffAtPaths function calls diff_at_path for a set of files and+-- and directories, and returns all changes to those files. It recurses into+-- given directories when searching for changes.+--+-- Comparing paths and not slurpies is useful when the user+-- requests a diff for a file that is created or removed in the working copy:+-- then there is no slurpy for the file in the /current/ or /working/ slurpy+-- respectively.+--+-- The given paths must always be fixed repository paths starting with a+-- ".". It is safe to pass overlapping paths.+--+-- The booleans in the first argument tell whether to ignore mtimes, whether+-- we must look for additions and if we're diffing for a summary only.+--+-- It returns an FL of patches, that contains all the changes that have been+-- made at all those paths.+unsafeDiffAtPaths :: (Bool, Bool, Bool) -> (FilePath -> FileType) ->+ Slurpy -> Slurpy -> [FilePath] -> FL Prim C(x y)+#ifdef GADT_WITNESSES+unsafeDiffAtPaths = undefined+#else+unsafeDiffAtPaths flags filetypeFunction s1 s2 paths =+ foldr (+>+) NilFL (catMaybes diffsPerPath)+ where diffsPerPath = map differ safePaths+ differ = diff_at_path flags filetypeFunction s1 s2+ safePaths = make_nonoverlapping_path_set paths++diff_at_path :: (Bool, Bool, Bool) -> (FilePath -> FileType)+ -> Slurpy -> Slurpy -> FilePath -> Maybe (FL Prim)+diff_at_path (ignoreTimes, lookForAdds, summary) filetypeFunction s1 s2 path =+ case (pathIn1, pathIn2) of+ (Nothing, Nothing) -> Nothing+ (Nothing, Just s2PathSlurpy) -> do+ Just $ diff_added summary filetypeFunction initialFps s2PathSlurpy NilFL+ (Just s1PathSlurpy, Nothing) -> do+ Just $ diff_removed filetypeFunction initialFps s1PathSlurpy NilFL+ (Just s1PathSlurpy, Just s2PathSlurpy) ->+ Just $ gendiff (ignoreTimes, lookForAdds, summary) filetypeFunction+ initialFps s1PathSlurpy s2PathSlurpy NilFL+ where pathIn1 = get_slurp (fp2fn path) s1+ pathIn2 = get_slurp (fp2fn path) s2+ initialFps = tail $ reverse (breakup path)++make_nonoverlapping_path_set :: [FilePath] -> [FilePath]+make_nonoverlapping_path_set = map unbreakup . delete_overlapping . map breakup . sort+ where+ delete_overlapping :: [[FilePath]] -> [[FilePath]]+ delete_overlapping (p1:p2:ps) = if p1 `isPrefixOf` p2+ then delete_overlapping (p1:ps)+ else p1 : delete_overlapping (p2:ps)+ delete_overlapping ps = ps+ unbreakup = concat . intersperse "/"+#endif++-- The diff function takes a recursive diff of two slurped-up directory trees.+-- The code involved is actually pretty trivial. \verb!paranoid_diff! runs a+-- diff in which we don't make the assumption that files with the same+-- modification time are identical.++unsafeDiff :: [DarcsFlag]+ -> (FilePath -> FileType) -> Slurpy -> Slurpy -> FL Prim C(x y)+#ifdef GADT_WITNESSES+unsafeDiff = undefined+#else+unsafeDiff opts wt s1 s2+ = case diff_at_path (ignoreTimes, lookForAdds, summary) wt s1 s2 "" of+ Just d -> d+ _ -> impossible -- because "" always exists in a slurpy + where -- NoSummary/Summary both present gives False+ -- Just Summary gives True+ -- Just NoSummary gives False+ -- Neither gives False+ summary = Summary `elem` opts && NoSummary `notElem` opts+ lookForAdds = LookForAdds `elem` opts+ ignoreTimes = IgnoreTimes `elem` opts++mk_filepath :: [FilePath] -> FilePath+mk_filepath fps = concat $ intersperse "/" $ reverse fps++gendiff :: (Bool,Bool,Bool)+ -> (FilePath -> FileType) -> [FilePath] -> Slurpy -> Slurpy+ -> (FL Prim -> FL Prim)+gendiff opts@(isparanoid,_,_) wt fps s1 s2+ | is_file s1 && is_file s2 = diff_regular_files isparanoid wt f s1 s2+ | is_dir s1 && is_dir s2 =+ let fps' = case n2 of+ "." -> fps+ _ -> n2:fps+ in fps' `seq` recur_diff opts (wt . (n2</>)) fps' dc1 dc2+ | otherwise = id+ where n2 = slurp_name s2+ f = mk_filepath (n2:fps)+ dc1 = get_dircontents s1+ dc2 = get_dircontents s2++-- recur_diff or recursive diff+-- First parameter is (IgnoreTimes?, LookforAdds?, Summary?)+recur_diff :: (Bool,Bool,Bool)+ -> (FilePath -> FileType) -> [FilePath] -> [Slurpy] -> [Slurpy]+ -> (FL Prim -> FL Prim)+recur_diff _ _ _ [] [] = id+recur_diff opts@(_,doadd,summary) wt fps (s:ss) (s':ss')+ -- this is the case if a file has been removed in the working directory+ | s < s' = diff_removed wt fps s . recur_diff opts wt fps ss (s':ss')+ -- this next case is when there is a file in the directory that is not+ -- in the repository (ie, not managed by darcs)+ | s > s' = let rest = recur_diff opts wt fps (s:ss) ss'+ in if not doadd then rest+ else diff_added summary wt fps s' . rest+ -- actually compare the files because the names match+ | s == s' = gendiff opts wt fps s s' . recur_diff opts wt fps ss ss'+recur_diff opts wt fps (s:ss) [] =+ diff_removed wt fps s . recur_diff opts wt fps ss []+recur_diff opts@(_,True,summary) wt fps [] (s':ss') =+ diff_added summary wt fps s' . recur_diff opts wt fps [] ss'+recur_diff (_,False,_) _ _ [] _ = id+recur_diff _ _ _ _ _ = impossible++-- diff, taking into account paranoidness and file type, two regular files+diff_regular_files :: Bool -> (FilePath -> FileType) -> FilePath -> Slurpy -> Slurpy -> (FL Prim -> FL Prim)+diff_regular_files ignoreTimes filetypeFunction f s1 s2 = + if maybe_differ + then case filetypeFunction (slurp_name s2) of + TextFile -> diff_files f b1 b2 + BinaryFile -> if b1 /= b2 then (binary f b1 b2:>:)+ else id + else id+ where maybe_differ = ignoreTimes+ || get_mtime s1 == undefined_time+ || get_mtime s1 /= get_mtime s2+ || get_length s1 == undefined_size+ || get_length s1 /= get_length s2+ b1 = get_filecontents s1+ b2 = get_filecontents s2++-- creates a diff for a file or directory which needs to be added to the+-- repository+diff_added :: Bool -> (FilePath -> FileType) -> [FilePath] -> Slurpy+ -> (FL Prim -> FL Prim)+diff_added summary wt fps s+ | is_file s = case wt n of+ TextFile -> (addfile f:>:) .+ (if summary+ then id+ else diff_from_empty id f (get_filecontents s))+ BinaryFile -> (addfile f:>:) .+ (if summary then id else+ (bin_patch f B.empty (get_filecontents s)))+ | otherwise {- is_dir s -} =+ (adddir f:>:)+ . foldr (.) id (map (diff_added summary wt (n:fps)) (get_dircontents s))+ where n = slurp_name s+ f = mk_filepath (n:fps)++get_text :: FileContents -> [B.ByteString]+get_text = linesPS++empt :: FileContents+empt = B.empty++diff_files :: FilePath -> FileContents -> FileContents+ -> (FL Prim -> FL Prim)+diff_files f o n | get_text o == [B.empty] && get_text n == [B.empty] = id+ | get_text o == [B.empty] = diff_from_empty id f n+ | get_text n == [B.empty] = diff_from_empty invert f o+diff_files f o n = if o == n+ then id+ else if has_bin o || has_bin n+ then (binary f o n:>:)+ else (canonize (hunk f 1 (linesPS o) (linesPS n)) +>+)++diff_from_empty :: (Prim -> Prim) -> FilePath -> FileContents+ -> (FL Prim -> FL Prim)+diff_from_empty inv f b =+ if b == B.empty+ then id+ else let p = if has_bin b+ then binary f B.empty b+ else if BC.last b == '\n'+ then hunk f 1 [] $ init $ linesPS b+ else hunk f 1 [B.empty] $ linesPS b+ in (inv p:>:)++{- | We take a B.ByteString which represents a file's contents, and we check to see+whether it is a 'binary' file or a 'textual' file. We define a textual file as any file+which does not contain two magic characters, '\0' (the NULL character on Unix) and '^Z'+(Control-Z, a DOS convention).++Note that to improve performance, we won't examine *all* of the string, because that+falls down on large files, but just the first 4096 characters. -}+has_bin :: FileContents -> Bool+has_bin = is_funky . B.take 4096+#endif++#ifndef GADT_WITNESSES+bin_patch :: FilePath -> B.ByteString -> B.ByteString+ -> FL Prim -> FL Prim+bin_patch f o n | B.null o && B.null n = id+ | otherwise = (binary f o n:>:)+#endif++#ifndef GADT_WITNESSES+diff_removed :: (FilePath -> FileType) -> [FilePath] -> Slurpy+ -> (FL Prim -> FL Prim)+diff_removed wt fps s+ | is_file s = case wt n of+ TextFile -> diff_files f (get_filecontents s) empt+ . (rmfile f:>:)+ BinaryFile -> (bin_patch f+ (get_filecontents s) B.empty)+ . (rmfile f:>:)+ | otherwise {- is_dir s -}+ = foldr (.) (rmdir f:>:)+ $ map (diff_removed wt (n:fps)) (get_dircontents s)+ where n = slurp_name s+ f = mk_filepath (n:fps)+#endif++sync :: String -> Slurpy -> Slurpy -> IO ()+sync path s1 s2+ | is_file s1 && is_file s2 &&+ (get_mtime s1 == undefined_time || get_mtime s1 /= get_mtime s2) &&+ get_length s1 == get_length s2 &&+ get_filecontents s1 == get_filecontents s2 =+ set_mtime n (get_mtime s2)+ | is_dir s1 && is_dir s2+ = n2 `seq` recur_sync n (get_dircontents s1) (get_dircontents s2)+ | otherwise = return ()+ where n2 = slurp_name s2+ n = path++"/"++n2+ set_mtime fname ctime = setFileTimes fname ctime ctime+ `catchall` return ()+ recur_sync _ [] _ = return ()+ recur_sync _ _ [] = return ()+ recur_sync p (s:ss) (s':ss')+ | s < s' = recur_sync p ss (s':ss')+ | s > s' = recur_sync p (s:ss) ss'+ | otherwise = do sync p s s'+ recur_sync p ss ss'+++cmp :: FilePath -> FilePath -> IO Bool+cmp p1 p2 = do+ dir1 <- doesDirectoryExist p1+ dir2 <- doesDirectoryExist p2+ file1 <- doesFileExist p1+ file2 <- doesFileExist p2+ if dir1 && dir2+ then cmpdir p1 p2+ else if file1 && file2+ then cmpfile p1 p2+ else return False+cmpdir :: FilePath -> FilePath -> IO Bool+cmpdir d1 d2 = do+ fn1 <- fmap (filter (\f->f/="." && f /="..")) $ getDirectoryContents d1+ fn2 <- fmap (filter (\f->f/="." && f /="..")) $ getDirectoryContents d2+ if sort fn1 /= sort fn2+ then return False+ else andIO $ map (\fn-> cmp (d1++"/"++fn) (d2++"/"++fn)) fn1+andIO :: [IO Bool] -> IO Bool+andIO (iob:iobs) = do b <- iob+ if b then andIO iobs else return False+andIO [] = return True+cmpfile :: FilePath -> FilePath -> IO Bool+cmpfile f1 f2 = do+ h1 <- openBinaryFile f1 ReadMode+ h2 <- openBinaryFile f2 ReadMode+ l1 <- hFileSize h1+ l2 <- hFileSize h2+ if l1 /= l2+ then do hClose h1+ hClose h2+ putStrLn $ "different file lengths for "++f1++" and "++f2+ return False+ else do b <- hcmp h1 h2+ when (not b) $ putStrLn $ "files "++f1++" and "++f2++" differ"+ hClose h1+ hClose h2+ return b+ where hcmp h1 h2 = do c1 <- B.hGet h1 1024+ c2 <- B.hGet h2 1024+ if c1 /= c2+ then return False+ else if B.length c1 == 1024+ then hcmp h1 h2+ else return True
@@ -0,0 +1,234 @@+{-# OPTIONS_GHC -cpp #-}+{-# LANGUAGE CPP #-}+module Darcs.Email ( make_email, read_email, formatHeader ) where++import Data.Char ( digitToInt, isHexDigit, ord, intToDigit, isPrint, toUpper )+import Data.List ( isInfixOf )+import qualified UTF8 ( encode )+import Printer ( Doc, ($$), (<+>), (<>), text, empty, packedString, renderPS)++import ByteStringUtils (dropSpace, linesPS, betweenLinesPS )+import qualified Data.ByteString as B (ByteString, length, null, tail+ ,drop, head, concat, singleton+ ,pack, append, empty+ )+import qualified Data.ByteString.Char8 as BC (index, head, pack)+import Data.ByteString.Internal as B (c2w, createAndTrim)+import System.IO.Unsafe ( unsafePerformIO )+import Foreign.Ptr ( Ptr, plusPtr )+import Foreign.Storable ( poke )+import Data.Word ( Word8 )++-- line_max is maximum number of characters in an e-mail line excluding the CRLF+-- at the end. qline_max is the number of characters in a q-encoded or+-- quoted-printable-encoded line.+line_max, qline_max :: Int+line_max = 78+qline_max = 75++-- | Formats an e-mail header by encoding any non-ascii characters using UTF-8+-- and Q-encoding, and folding lines at appropriate points. It doesn't do+-- more than that, so the header name and header value should be+-- well-formatted give or take line length and encoding. So no non-ASCII+-- characters within quoted-string, quoted-pair, or atom; no semantically+-- meaningful signs in names; no non-ASCII characters in the header name;+-- etcetera.+formatHeader :: String -> String -> B.ByteString+formatHeader headerName headerValue =+ B.append nameColon encodedValue+ where nameColon = B.pack (map B.c2w (headerName ++ ":")) -- space for folding+ encodedValue = fold_and_encode (' ':headerValue)+ (B.length nameColon) False False++-- run through a string and encode non-ascii words and fold where appropriate.+-- the integer argument is the current position in the current line.+-- the string in the first argument must begin with whitespace, or be empty.+fold_and_encode :: String -> Int -> Bool -> Bool -> B.ByteString+fold_and_encode [] _ _ _ = B.empty+fold_and_encode s p lastWordEncoded inMidWord = + let newline = B.singleton 10+ space = B.singleton 32+ s2bs = B.pack . map B.c2w+ -- the twelve there is the max number of ASCII chars to encode a single+ -- character: 4 * 3, 4 UTF-8 bytes times 3 ASCII chars per byte+ safeEncChunkLength = (qline_max - B.length encoded_word_start+ - B.length encoded_word_end) `div` 12+ (curSpace, afterCurSpace) = break (not . (== ' ')) s+ (curWord, afterCurWord) = break (== ' ') afterCurSpace+ qEncWord | lastWordEncoded = qEncode (curSpace ++ curWord)+ | otherwise = qEncode curWord+ mustEncode = inMidWord+ || any (\c -> not (isPrint c) || (ord c) > 127) curWord+ || length curWord > line_max - 1+ || isInfixOf "=?" curWord+ mustFold+ | mustEncode && lastWordEncoded+ = p + 1 + B.length qEncWord > line_max+ | mustEncode+ = p + length curSpace + B.length qEncWord > line_max+ | otherwise+ = p + length curSpace + length curWord > line_max+ mustSplit = (B.length qEncWord > qline_max && mustEncode)+ || length curWord > line_max - 1+ spaceToInsert | mustEncode && lastWordEncoded = space+ | otherwise = s2bs curSpace+ wordToInsert+ | mustEncode && mustSplit = qEncode (take safeEncChunkLength curWord)+ | mustEncode = qEncWord+ | otherwise = s2bs curWord+ doneChunk | mustFold = B.concat [newline, spaceToInsert, wordToInsert]+ | otherwise = B.concat [spaceToInsert, wordToInsert]+ (rest, nextP)+ | mustSplit+ = (drop safeEncChunkLength curWord ++ afterCurWord, qline_max + 1)+ | mustEncode && mustFold + = (afterCurWord, B.length spaceToInsert + B.length wordToInsert)+ | otherwise+ = (afterCurWord, p + B.length doneChunk)+ in B.append doneChunk (fold_and_encode rest nextP mustEncode mustSplit)++-- | Turns a piece of string into a q-encoded block+-- Applies q-encoding, for use in e-mail header values, as defined in RFC 2047.+-- It just takes a string and builds an encoded-word from it, it does not check+-- length or necessity.+qEncode :: String -> B.ByteString+qEncode s = B.concat [encoded_word_start,+ encodedString,+ encoded_word_end]+ where encodedString = B.concat (map q_encode_char s)++encoded_word_start, encoded_word_end :: B.ByteString+encoded_word_start = B.pack (map B.c2w "=?UTF-8?Q?")+encoded_word_end = B.pack (map B.c2w "?=")++-- turns a character into its q-encoded bytestring value. For most printable+-- ASCII characters, that's just the singleton bytestring with that char.+q_encode_char :: Char -> B.ByteString+q_encode_char c+ | c == ' ' = c2bs '_'+ | isPrint c+ && not (c `elem` ['?', '=', '_'])+ && ord c < 128 = c2bs c+ | otherwise = B.concat (map qbyte (UTF8.encode [c]))+ where c2bs = B.singleton . B.c2w+ -- qbyte turns a byte into its q-encoded "=hh" representation+ qbyte b = B.pack (map B.c2w ['='+ ,word8ToUDigit (b `div` 16)+ ,word8ToUDigit (b `mod` 16)+ ])+ word8ToUDigit :: Word8 -> Char+ word8ToUDigit = toUpper . intToDigit . fromIntegral++-- TODO is this doing mime encoding??+qpencode :: B.ByteString -> B.ByteString+qpencode s = unsafePerformIO+ -- Really only (3 + 2/75) * length or something in the worst case+ $ B.createAndTrim (4 * B.length s) (\buf -> encode s qline_max buf 0)++encode :: B.ByteString -> Int -> Ptr Word8 -> Int -> IO Int+encode ps _ _ bufi | B.null ps = return bufi+encode ps n buf bufi = case B.head ps of+ c | c == newline ->+ do poke (buf `plusPtr` bufi) newline+ encode ps' qline_max buf (bufi+1)+ | n == 0 && B.length ps > 1 ->+ do poke (buf `plusPtr` bufi) equals+ poke (buf `plusPtr` (bufi+1)) newline+ encode ps qline_max buf (bufi + 2)+ | (c == tab || c == space) ->+ if B.null ps' || B.head ps' == newline+ then do poke (buf `plusPtr` bufi) c+ poke (buf `plusPtr` (bufi+1)) equals+ poke (buf `plusPtr` (bufi+2)) newline+ encode ps' qline_max buf (bufi + 3)+ else do poke (buf `plusPtr` bufi) c+ encode ps' (n - 1) buf (bufi + 1)+ | (c >= bang && c /= equals && c <= tilde) ->+ do poke (buf `plusPtr` bufi) c+ encode ps' (n - 1) buf (bufi + 1)+ | n < 3 ->+ encode ps 0 buf bufi+ | otherwise ->+ do let (x, y) = c `divMod` 16+ h1 = intToUDigit x+ h2 = intToUDigit y+ poke (buf `plusPtr` bufi) equals+ poke (buf `plusPtr` (bufi+1)) h1+ poke (buf `plusPtr` (bufi+2)) h2+ encode ps' (n - 3) buf (bufi + 3)+ where ps' = B.tail ps+ newline = B.c2w '\n'+ tab = B.c2w '\t'+ space = B.c2w ' '+ bang = B.c2w '!'+ tilde = B.c2w '~'+ equals = B.c2w '='+ intToUDigit i+ | i >= 0 && i <= 9 = B.c2w '0' + i+ | i >= 10 && i <= 15 = B.c2w 'A' + i - 10+ | otherwise = error $ "intToUDigit: '"++show i++"'not a digit"++qpdecode :: B.ByteString -> B.ByteString+qpdecode s = unsafePerformIO+ -- Add 1 as linesPS "\n" -> ["", ""] -> "\n\n"+ $ B.createAndTrim (B.length s + 1) (\buf -> decode (linesPS s) buf 0)++decode :: [B.ByteString] -> Ptr Word8 -> Int -> IO Int+decode [] _ bufi = return bufi+decode (ps:pss) buf bufi+ | B.null (dropSpace ps)+ = do poke (buf `plusPtr` bufi) newline+ decode pss buf (bufi+1)+ | is_equals && B.length ps >= 3 && isHexDigit c1 && isHexDigit c2+ = do poke (buf `plusPtr` bufi)+ (toWord8 $ digitToInt c1 * 16 + digitToInt c2)+ decode (B.drop 3 ps:pss) buf (bufi+1)+ | is_equals && B.null (dropSpace (B.tail ps)) = decode pss buf bufi+ | otherwise = do poke (buf `plusPtr` bufi) (B.head ps)+ decode (B.tail ps:pss) buf (bufi+1)+ where is_equals = BC.head ps == '='+ c1 = BC.index ps 1+ c2 = BC.index ps 2+ newline = B.c2w '\n'+ toWord8 :: Int -> Word8+ toWord8 = fromIntegral++make_email :: String -> [(String, String)] -> (Maybe Doc) -> Doc -> (Maybe String) -> Doc+make_email repodir headers mcontents bundle mfilename =+ text "DarcsURL:" <+> text repodir+ $$ foldl (\m (h,v) -> m $$ (text (h ++ ":") <+> text v)) empty headers+ $$ text "MIME-Version: 1.0"+ $$ text "Content-Type: multipart/mixed; boundary=\"=_\""+ $$ text ""+ $$ text "--=_"+ $$ (case mcontents of+ Just contents ->+ text "Content-Type: text/plain"+ $$ text "Content-Transfer-Encoding: quoted-printable"+ $$ text ""+ $$ packedString (qpencode (renderPS contents))+ $$ text ""+ $$ text "--=_"+ Nothing -> empty)+ $$ text "Content-Type: text/x-darcs-patch" <>+ (case mfilename of+ Just filename -> text "; name=\"" <> text filename <> text "\""+ Nothing -> empty)+ $$ text "Content-Transfer-Encoding: quoted-printable"+ $$ text "Content-Description: A darcs patch for your repository!"+ $$ text ""+ $$ packedString (qpencode (renderPS bundle))+ $$ text "--=_--"+ $$ text ""+ $$ text "."+ $$ text ""+ $$ text ""++read_email :: B.ByteString -> B.ByteString+read_email s =+ case betweenLinesPS+ (BC.pack "Content-Description: A darcs patch for your repository!")+ (BC.pack "--=_--") s of+ Nothing -> s -- if it wasn't an email in the first place, just pass along.+ Just s' -> qpdecode s'+
@@ -0,0 +1,656 @@+{-# LANGUAGE CPP, ForeignFunctionInterface #-}++module Darcs.External (+ backupByRenaming, backupByCopying,+ copyFileOrUrl, speculateFileOrUrl, copyFilesOrUrls, copyLocal, cloneFile,+ cloneTree, cloneTreeExcept, clonePartialsTree, clonePaths,+ fetchFilePS, gzFetchFilePS,+ sendEmail, generateEmail, sendEmailDoc, resendEmail,+ signString, verifyPS,+ execDocPipe, execPipeIgnoreError,+ getTermNColors,+ pipeDoc, pipeDocSSH, execSSH,+ maybeURLCmd,+ Cachable(Cachable, Uncachable, MaxAge),+ viewDoc, viewDocWith,+ sendmail_path, diff_program+ ) where++import Data.Maybe ( isJust, isNothing, maybeToList )+import Control.Monad ( when, zipWithM_, filterM, liftM2 )+import System.Exit ( ExitCode(..) )+import System.Environment ( getEnv )+import System.IO ( hPutStr, hPutStrLn, hGetContents, hClose,+ openBinaryFile, IOMode( ReadMode ),+ openBinaryTempFile,+ hIsTerminalDevice, stdout, stderr, Handle )+import System.IO.Error ( isDoesNotExistError )+import System.Posix.Files ( getSymbolicLinkStatus, isRegularFile, isDirectory )+import System.Directory ( createDirectory, getDirectoryContents,+ doesFileExist, doesDirectoryExist,+ renameFile, renameDirectory, copyFile,+ findExecutable )+import System.Process ( runProcess, runInteractiveProcess, waitForProcess )+import Control.Concurrent ( forkIO, newEmptyMVar, putMVar, takeMVar )+import Control.Exception ( bracket, try, finally )+import Data.Char ( toUpper )+#if defined (HAVE_MAPI)+import Foreign.C ( CString, withCString )+#endif+#ifdef HAVE_MAPI+import Foreign.Ptr ( nullPtr )+import Darcs.Lock ( canonFilename, writeDocBinFile )+#endif+#ifdef HAVE_TERMINFO+import System.Console.Terminfo( tiGetNum, setupTermFromEnv, getCapability )+#endif+import System.Posix.Files ( createLink )+import System.Directory ( createDirectoryIfMissing )+import System.FilePath.Posix ( (</>), takeDirectory, normalise )++import Darcs.Flags ( DarcsFlag( SignAs, Sign, SignSSL, NoLinks,+ Verify, VerifySSL ) )+import Darcs.RepoPath ( AbsolutePath, toFilePath )+import Darcs.Utils ( withCurrentDirectory, breakCommand, get_viewer, ortryrunning, )+import Progress ( withoutProgress, progressList, debugMessage )++import ByteStringUtils (gzReadFilePS, linesPS, unlinesPS)+import qualified Data.ByteString as B (ByteString, empty, null, readFile -- ratify readFile: Just an import from ByteString+ ,hGetContents, writeFile, hPut, length -- ratify hGetContents: importing from ByteString+ ,take, concat, drop, isPrefixOf, singleton, append)+import qualified Data.ByteString.Char8 as BC (unpack, pack)++import Darcs.Lock ( withTemp, withOpenTemp, tempdir_loc, removeFileMayNotExist )+import CommandLine ( parseCmd, addUrlencoded )+import URL ( copyUrl, copyUrlFirst, waitUrl )+import Ssh ( getSSH, copySSH, copySSHs, SSHCmd(..) )+import URL ( Cachable(..) )+import Exec ( exec, Redirect(..), withoutNonBlock )+import Darcs.URL ( is_file, is_url, is_ssh )+import Darcs.Utils ( catchall )+import Printer ( Doc, Printers, putDocLnWith, hPutDoc, hPutDocLn, hPutDocWith, ($$), renderPS,+ simplePrinters,+ text, empty, packedString, vcat, renderString )+import Darcs.Email ( formatHeader )++sendmail_path :: IO String+sendmail_path = do+ l <- filterM doesFileExist $ liftM2 (</>)+ [ "/usr/sbin", "/sbin", "/usr/lib" ]+ [ "sendmail" ]+ ex <- findExecutable "sendmail"+ when (isNothing ex && null l) $ fail "Cannot find the \"sendmail\" program."+ return $ head $ maybeToList ex ++ l++diff_program :: IO String+diff_program = do+ l <- filterM (fmap isJust . findExecutable) [ "gdiff", "gnudiff", "diff" ]+ when (null l) $ fail "Cannot find the \"diff\" program."+ return $ head l++backupByRenaming :: FilePath -> IO ()+backupByRenaming = backupBy rename+ where rename x y = do+ isD <- doesDirectoryExist x+ if isD then renameDirectory x y else renameFile x y++backupByCopying :: FilePath -> IO ()+backupByCopying = backupBy copy+ where+ copy x y = do+ isD <- doesDirectoryExist x+ if isD then do createDirectory y+ cloneTree (normalise x) (normalise y)+ else copyFile x y++backupBy :: (FilePath -> FilePath -> IO ()) -> FilePath -> IO ()+backupBy backup f =+ do hasBF <- doesFileExist f+ hasBD <- doesDirectoryExist f+ when (hasBF || hasBD) $ helper (0::Int)+ where+ helper i = do existsF <- doesFileExist next+ existsD <- doesDirectoryExist next+ if (existsF || existsD)+ then helper (i + 1)+ else do putStrLn $ "Backing up " ++ f ++ "(" ++ suffix ++ ")"+ backup f next+ where next = f ++ suffix+ suffix = "-darcs-backup" ++ show i++-- | @fetchFile fileOrUrl cache@ returns the content of its argument+-- (either a file or an URL). If it has to download an url, then it+-- will use a cache as required by its second argument.+fetchFilePS :: String -> Cachable -> IO B.ByteString+fetchFilePS fou _ | is_file fou = B.readFile fou+fetchFilePS fou cache = withTemp $ \t -> do copyFileOrUrl [] fou t cache+ B.readFile t++gzFetchFilePS :: String -> Cachable -> IO B.ByteString+gzFetchFilePS fou _ | is_file fou = gzReadFilePS fou+gzFetchFilePS fou cache = withTemp $ \t-> do copyFileOrUrl [] fou t cache+ gzReadFilePS t+++copyFileOrUrl :: [DarcsFlag] -> FilePath -> FilePath -> Cachable -> IO ()+copyFileOrUrl opts fou out _ | is_file fou = copyLocal opts fou out+copyFileOrUrl _ fou out cache | is_url fou = copyRemote fou out cache+copyFileOrUrl _ fou out _ | is_ssh fou = copySSH fou out+copyFileOrUrl _ fou _ _ = fail $ "unknown transport protocol: " ++ fou++speculateFileOrUrl :: String -> FilePath -> IO ()+speculateFileOrUrl fou out | is_url fou = speculateRemote fou out+ | otherwise = return ()++copyLocal :: [DarcsFlag] -> String -> FilePath -> IO ()+copyLocal opts fou out | NoLinks `elem` opts = cloneFile fou out+ | otherwise = createLink fou out `catchall` cloneFile fou out++clonePaths :: FilePath -> FilePath -> [FilePath] -> IO ()+clonePaths source dest = mapM_ (clonePath source dest)++clonePath :: FilePath -> FilePath -> FilePath -> IO ()+clonePath source dest path+ = do let source' = source </> path+ dest' = dest </> path+ fs <- getSymbolicLinkStatus source'+ if isDirectory fs then do+ createDirectoryIfMissing True dest'+ else if isRegularFile fs then do+ createDirectoryIfMissing True (dest </> takeDirectory path)+ cloneFile source' dest'+ else fail ("clonePath: Bad file " ++ source')+ `catch` fail ("clonePath: Bad file " ++ source </> path)++clonePartialsTree :: FilePath -> FilePath -> [FilePath] -> IO ()+clonePartialsTree source dest = mapM_ (clonePartialTree source dest)++clonePartialTree :: FilePath -> FilePath -> FilePath -> IO ()+clonePartialTree source dest "" = cloneTree source dest+clonePartialTree source dest pref+ = do createDirectoryIfMissing True (dest </> takeDirectory pref)+ cloneSubTree (source </> pref) (dest </> pref)++cloneTree :: FilePath -> FilePath -> IO ()+cloneTree = cloneTreeExcept []++cloneTreeExcept :: [FilePath] -> FilePath -> FilePath -> IO ()+cloneTreeExcept except source dest =+ do fs <- getSymbolicLinkStatus source+ if isDirectory fs then do+ fps <- getDirectoryContents source+ let fps' = filter (`notElem` (".":"..":except)) fps+ mk_source fp = source </> fp+ mk_dest fp = dest </> fp+ zipWithM_ cloneSubTree (map mk_source fps') (map mk_dest fps')+ else fail ("cloneTreeExcept: Bad source " ++ source)+ `catch` fail ("cloneTreeExcept: Bad source " ++ source)++cloneSubTree :: FilePath -> FilePath -> IO ()+cloneSubTree source dest =+ do fs <- getSymbolicLinkStatus source+ if isDirectory fs then do+ createDirectory dest+ fps <- getDirectoryContents source+ let fps' = filter (`notElem` [".", ".."]) fps+ mk_source fp = source </> fp+ mk_dest fp = dest </> fp+ zipWithM_ cloneSubTree (map mk_source fps') (map mk_dest fps')+ else if isRegularFile fs then do+ cloneFile source dest+ else fail ("cloneSubTree: Bad source "++ source)+ `catch` (\e -> if isDoesNotExistError e+ then return ()+ else ioError e)++cloneFile :: FilePath -> FilePath -> IO ()+cloneFile = copyFile++maybeURLCmd :: String -> String -> IO(Maybe(String))+maybeURLCmd what url =+ do let prot = map toUpper $ takeWhile (/= ':') url+ fmap Just (getEnv ("DARCS_" ++ what ++ "_" ++ prot))+ `catch` \_ -> return Nothing++speculateRemote :: String -> FilePath -> IO () -- speculations are always Cachable+#if defined(HAVE_CURL) || defined(HAVE_HTTP)+speculateRemote u v =+ do maybeget <- maybeURLCmd "GET" u+ case maybeget of+ Just _ -> return () -- can't pipeline these+ Nothing -> copyUrl u v Cachable+#else+speculateRemote u _ = maybeURLCmd "GET" u >> return ()+#endif++copyRemote :: String -> FilePath -> Cachable -> IO ()+copyRemote u v cache =+ do maybeget <- maybeURLCmd "GET" u+ case maybeget of+ Nothing -> copyRemoteNormal u v cache+ Just get ->+ do let (cmd,args) = breakCommand get+ r <- exec cmd (args++[u]) (Null, File v, AsIs)+ when (r /= ExitSuccess) $+ fail $ "(" ++ get ++ ") failed to fetch: " ++ u++copyRemoteNormal :: String -> FilePath -> Cachable -> IO ()+copyRemoteNormal u v cache = copyUrlFirst u v cache >> waitUrl u++copyFilesOrUrls :: [DarcsFlag]->FilePath->[String]->FilePath->Cachable->IO ()+copyFilesOrUrls opts dou ns out _ | is_file dou = copyLocals opts dou ns out+copyFilesOrUrls _ dou ns out c | is_url dou = copyRemotes dou ns out c+copyFilesOrUrls _ dou ns out _ | is_ssh dou = copySSHs dou ns out+copyFilesOrUrls _ dou _ _ _ = fail $ "unknown transport protocol: "++dou+++copyLocals :: [DarcsFlag] -> String -> [String] -> FilePath -> IO ()+copyLocals opts u ns d =+ doWithPatches (\n -> copyLocal opts (u++"/"++n) (d++"/"++n)) ns++copyRemotes :: String -> [String] -> FilePath -> Cachable -> IO()+copyRemotes u ns d cache =+ do maybeget <- maybeURLCmd "GET" u+ maybemget <- maybeURLCmd "MGET" u+ case (maybeget, maybemget) of+ (Nothing, _) -> copyRemotesNormal u ns d cache+ (Just _, Nothing) -> doWithPatches (\n -> copyRemote (u++"/"++n) (d++"/"++n) cache) ns+ (Just _, Just mget) -> mgetRemotes mget u ns d++stringToInt :: String -> Int -> Int+stringToInt num def = case reads num of [(x,"")] -> x+ _ -> def++mgetRemotes :: String -> String -> [String] -> FilePath -> IO()+mgetRemotes _ _ [] _ = return ()+mgetRemotes mget u ns d = do+ mgetmax <- getEnv "DARCS_MGETMAX" `catch` \_ -> return ""+ let (nsnow, nslater) = splitAt (stringToInt mgetmax 200) ns+ (cmd, args) = breakCommand mget+ urls = map (\n -> u++"/"++n) nsnow+ withCurrentDirectory d $ do+ r <- exec cmd (args++urls) (Null,Null,AsIs)+ when (r /= ExitSuccess) $+ fail $ unlines $+ ["(" ++ mget ++ ") failed to fetch files.",+ "source directory: " ++ d,+ "source files:"] ++ (upto 5 nsnow) +++ ["still to go:"] ++ (upto 5 nslater)+ mgetRemotes mget u nslater d+ where+ upto :: Integer -> [String] -> [String]+ upto _ [] = []+ upto 0 l = [ "(" ++ (show (length l)) ++ " more)" ]+ upto n (h : t) = h : (upto (n - 1) t)++copyRemotesNormal :: String -> [String] -> FilePath -> Cachable -> IO()+copyRemotesNormal u ns d cache =+ do mapM_ (\n -> copyUrl (u++"/"++n) (d++"/"++n) cache) ns+ doWithPatches (\n -> waitUrl (u++"/"++n)) ns++doWithPatches :: (String -> IO ()) -> [String] -> IO ()+doWithPatches f patches = mapM_ (\p -> seq p $ f p) $ progressList "Copying patch" patches++-- | Run a command on a remote location without passing it any input or+-- reading its output. Return its ExitCode+execSSH :: String -> String -> IO ExitCode+execSSH remoteAddr command =+ do (ssh, ssh_args) <- getSSH SSH remoteAddr+ debugMessage $ unwords (ssh:ssh_args++[remoteAddr,command])+ withoutProgress $ do hid <- runProcess ssh (ssh_args++[remoteAddr,command])+ Nothing Nothing Nothing Nothing Nothing+ waitForProcess hid++pipeDoc :: String -> [String] -> Doc -> IO ExitCode+pipeDoc c args inp = withoutNonBlock $ withoutProgress $+ do debugMessage $ unwords (c:args)+ (i,o,e,pid) <- runInteractiveProcess c args Nothing Nothing+ mvare <- newEmptyMVar+ forkIO ((hGetContents e >>= -- ratify hGetContents: it's immediately consumed+ hPutStr stderr)+ `finally` putMVar mvare ())+ mvaro <- newEmptyMVar+ forkIO ((hGetContents o >>= -- ratify hGetContents: it's immediately consumed+ hPutStr stdout)+ `finally` putMVar mvaro ())+ hPutDoc i inp+ hClose i+ rval <- waitForProcess pid+ takeMVar mvare+ takeMVar mvaro+ when (rval == ExitFailure 127) $+ putStrLn $ "Command not found:\n "++ show (c:args)+ return rval++pipeDocSSH :: String -> [String] -> Doc -> IO ExitCode+pipeDocSSH remoteAddr args input =+ do (ssh, ssh_args) <- getSSH SSH remoteAddr+ pipeDoc ssh (ssh_args++ (remoteAddr:args)) input++sendEmail :: String -> String -> String -> String -> String -> String -> IO ()+sendEmail f t s cc scmd body =+ sendEmailDoc f t s cc scmd Nothing (text body)+++generateEmail+ :: Handle -- ^ handle to write email to+ -> String -- ^ From+ -> String -- ^ To+ -> String -- ^ Subject+ -> String -- ^ CC+ -> Doc -- ^ body+ -> IO ()+generateEmail h f t s cc body = do+ putHeader "To" t+ putHeader "From" f+ putHeader "Subject" s+ when (not (null cc)) (putHeader "Cc" cc)+ putHeader "X-Mail-Originator" "Darcs Version Control System"+ hPutDocLn h body+ where putHeader field value+ = B.hPut h (B.append (formatHeader field value) newline)+ newline = B.singleton 10++have_sendmail :: IO Bool+have_sendmail = (sendmail_path >> return True) `catch` (\_ -> return False)++-- | Send an email, optionally containing a patch bundle+-- (more precisely, its description and the bundle itself)+sendEmailDoc+ :: String -- ^ from+ -> String -- ^ to+ -> String -- ^ subject+ -> String -- ^ cc+ -> String -- ^ send command+ -> Maybe (Doc, Doc) -- ^ (content,bundle)+ -> Doc -- ^ body+ -> IO ()+sendEmailDoc _ "" _ "" _ _ _ = return ()+sendEmailDoc f "" s cc scmd mbundle body =+ sendEmailDoc f cc s "" scmd mbundle body+sendEmailDoc f t s cc scmd mbundle body = do+ use_sendmail <- have_sendmail+ if use_sendmail || scmd /= "" then do+ withOpenTemp $ \(h,fn) -> do+ generateEmail h f t s cc body+ hClose h+ withOpenTemp $ \(hat,at) -> do+ ftable' <- case mbundle of+ Just (content,bundle) -> do+ hPutDocLn hat $ bundle+ return [ ('b', renderString content) , ('a', at) ]+ Nothing ->+ return [ ('b', renderString body) ]+ hClose hat+ let ftable = [ ('t',addressOnly t),('c',cc),('f',f),('s',s) ] ++ ftable'+ r <- execSendmail ftable scmd fn+ when (r /= ExitSuccess) $ fail ("failed to send mail to: "+ ++ t ++ cc_list cc+ ++ "\nPerhaps sendmail is not configured.")+#ifdef HAVE_MAPI+ else do+ r <- withCString t $ \tp ->+ withCString f $ \fp ->+ withCString cc $ \ccp ->+ withCString s $ \sp ->+ withOpenTemp $ \(h,fn) -> do+ hPutDoc h body+ hClose h+ writeDocBinFile "mailed_patch" body+ cfn <- canonFilename fn+ withCString cfn $ \pcfn ->+ c_send_email fp tp ccp sp nullPtr pcfn+ when (r /= 0) $ fail ("failed to send mail to: " ++ t)+#else+ else fail $ "no mail facility (sendmail or mapi) located at configure time!"+#endif+ where addressOnly a =+ case dropWhile (/= '<') a of+ ('<':a2) -> takeWhile (/= '>') a2+ _ -> a++ cc_list [] = []+ cc_list c = " and cc'ed " ++ c++resendEmail :: String -> String -> B.ByteString -> IO ()+resendEmail "" _ _ = return ()+resendEmail t scmd body = do+ use_sendmail <- have_sendmail+ if use_sendmail || scmd /= ""+ then do+ withOpenTemp $ \(h,fn) -> do+ hPutStrLn h $ "To: "++ t+ hPutStrLn h $ find_from (linesPS body)+ hPutStrLn h $ find_subject (linesPS body)+ hPutDocLn h $ fixit $ linesPS body+ hClose h+ let ftable = [('t',t)]+ r <- execSendmail ftable scmd fn+ when (r /= ExitSuccess) $ fail ("failed to send mail to: " ++ t)+ else+#ifdef HAVE_MAPI+ fail "Don't know how to resend email with MAPI"+#else+ fail "no mail facility (sendmail or mapi) located at configure time (use the sendmail-command option)!"+#endif+ where br = BC.pack "\r"+ darcsurl = BC.pack "DarcsURL:"+ content = BC.pack "Content-"+ from_start = BC.pack "From:"+ subject_start = BC.pack "Subject:"+ fixit (l:ls)+ | B.null l = packedString B.empty $$ vcat (map packedString ls)+ | l == br = packedString B.empty $$ vcat (map packedString ls)+ | B.take 9 l == darcsurl || B.take 8 l == content+ = packedString l $$ fixit ls+ | otherwise = fixit ls+ fixit [] = empty+ find_from (l:ls) | B.take 5 l == from_start = BC.unpack l+ | otherwise = find_from ls+ find_from [] = "From: unknown"+ find_subject (l:ls) | B.take 8 l == subject_start = BC.unpack l+ | otherwise = find_subject ls+ find_subject [] = "Subject: (no subject)"++execSendmail :: [(Char,String)] -> String -> String -> IO ExitCode+execSendmail ftable scmd fn =+ if scmd == "" then do+ cmd <- sendmail_path+ exec cmd ["-i", "-t"] (File fn, Null, AsIs)+ else case parseCmd (addUrlencoded ftable) scmd of+ Right (arg0:opts, wantstdin) ->+ do let stdin = if wantstdin then File fn else Null+ exec arg0 opts (stdin, Null, AsIs)+ Left e -> fail $ ("failed to send mail, invalid sendmail-command: "++(show e))+ _ -> fail $ ("failed to send mail, invalid sendmail-command")++#ifdef HAVE_MAPI+foreign import ccall "win32/send_email.h send_email" c_send_email+ :: CString -> {- sender -}+ CString -> {- recipient -}+ CString -> {- cc -}+ CString -> {- subject -}+ CString -> {- body -}+ CString -> {- path -}+ IO Int+#endif++execPSPipe :: String -> [String] -> B.ByteString -> IO B.ByteString+execPSPipe c args ps = fmap renderPS+ $ execDocPipe c args+ $ packedString ps++execDocPipe :: String -> [String] -> Doc -> IO Doc+execDocPipe c args instr = withoutProgress $+ do (i,o,e,pid) <- runInteractiveProcess c args Nothing Nothing+ forkIO $ hPutDoc i instr >> hClose i+ mvare <- newEmptyMVar+ forkIO ((hGetContents e >>= -- ratify hGetContents: it's immediately consumed+ hPutStr stderr)+ `finally` putMVar mvare ())+ out <- B.hGetContents o+ rval <- waitForProcess pid+ takeMVar mvare+ case rval of+ ExitFailure ec ->fail $ "External program '"++c+++ "' failed with exit code "++ show ec+ ExitSuccess -> return $ packedString out++-- The following is needed for diff, which returns non-zero whenever+-- the files differ.+execPipeIgnoreError :: String -> [String] -> Doc -> IO Doc+execPipeIgnoreError c args instr = withoutProgress $+ do (i,o,e,pid) <- runInteractiveProcess c args Nothing Nothing+ forkIO $ hPutDoc i instr >> hClose i+ mvare <- newEmptyMVar+ forkIO ((hGetContents e >>= -- ratify hGetContents: it's immediately consumed+ hPutStr stderr)+ `finally` putMVar mvare ())+ out <- B.hGetContents o+ waitForProcess pid+ takeMVar mvare+ return $ packedString out++signString :: [DarcsFlag] -> Doc -> IO Doc+signString [] d = return d+signString (Sign:_) d = signPGP [] d+signString (SignAs keyid:_) d = signPGP ["--local-user", keyid] d+signString (SignSSL idf:_) d = signSSL idf d+signString (_:os) d = signString os d++signPGP :: [String] -> Doc -> IO Doc+signPGP args t = execDocPipe "gpg" ("--clearsign":args) t++signSSL :: String -> Doc -> IO Doc+signSSL idfile t =+ withTemp $ \cert -> do+ opensslPS ["req", "-new", "-key", idfile,+ "-outform", "PEM", "-days", "365"]+ (BC.pack "\n\n\n\n\n\n\n\n\n\n\n")+ >>= opensslPS ["x509", "-req", "-extensions",+ "v3_ca", "-signkey", idfile,+ "-outform", "PEM", "-days", "365"]+ >>= opensslPS ["x509", "-outform", "PEM"]+ >>= B.writeFile cert+ opensslDoc ["smime", "-sign", "-signer", cert,+ "-inkey", idfile, "-noattr", "-text"] t+ where opensslDoc = execDocPipe "openssl"+ opensslPS = execPSPipe "openssl"+++verifyPS :: [DarcsFlag] -> B.ByteString -> IO (Maybe B.ByteString)+verifyPS [] ps = return $ Just ps+verifyPS (Verify pks:_) ps = verifyGPG pks ps+verifyPS (VerifySSL auks:_) ps = verifySSL auks ps+verifyPS (_:os) ps = verifyPS os ps++verifyGPG :: AbsolutePath -> B.ByteString -> IO (Maybe B.ByteString)+verifyGPG goodkeys s =+ withOpenTemp $ \(th,tn) -> do+ B.hPut th s+ hClose th+ rval <- exec "gpg" ["--batch","--no-default-keyring",+ "--keyring",fix_path $ toFilePath goodkeys, "--verify"]+ (File tn, Null, Null)+ case rval of+ ExitSuccess -> return $ Just gpg_fixed_s+ _ -> return Nothing+ where gpg_fixed_s = let+ not_begin_signature x =+ x /= BC.pack "-----BEGIN PGP SIGNED MESSAGE-----"+ &&+ x /= BC.pack "-----BEGIN PGP SIGNED MESSAGE-----\r"+ in unlinesPS $ map fix_line $ tail $ dropWhile not_begin_signature $ linesPS s+ fix_line x | B.length x < 3 = x+ | BC.pack "- -" `B.isPrefixOf` x = B.drop 2 x+ | otherwise = x+#if defined(WIN32)+ fix_sep c | c=='/' = '\\' | otherwise = c+ fix_path p = map fix_sep p+#else+ fix_path p = p+#endif++verifySSL :: AbsolutePath -> B.ByteString -> IO (Maybe B.ByteString)+verifySSL goodkeys s = do+ certdata <- opensslPS ["smime", "-pk7out"] s+ >>= opensslPS ["pkcs7", "-print_certs"]+ cruddy_pk <- opensslPS ["x509", "-pubkey"] certdata+ let key_used = B.concat $ tail $+ takeWhile (/= BC.pack"-----END PUBLIC KEY-----")+ $ linesPS cruddy_pk+ in do allowed_keys <- linesPS `fmap` B.readFile (toFilePath goodkeys)+ if not $ key_used `elem` allowed_keys+ then return Nothing -- Not an allowed key!+ else withTemp $ \cert ->+ withTemp $ \on ->+ withOpenTemp $ \(th,tn) -> do+ B.hPut th s+ hClose th+ B.writeFile cert certdata+ rval <- exec "openssl" ["smime", "-verify", "-CAfile",+ cert, "-certfile", cert]+ (File tn, File on, Null)+ case rval of+ ExitSuccess -> Just `fmap` B.readFile on+ _ -> return Nothing+ where opensslPS = execPSPipe "openssl"+++{-+ - This function returns number of colors supported by current terminal+ - or -1 if color output not supported or error occured.+ - Terminal type determined by TERM env. variable.+ -}+getTermNColors :: IO Int+#ifdef HAVE_TERMINFO+getTermNColors = do+ t <- setupTermFromEnv+ return $ case getCapability t $ tiGetNum "colors" of+ Nothing -> (-1)+ Just x -> x+#else+getTermNColors = return (-1)+#endif++viewDoc :: Doc -> IO ()+viewDoc = viewDocWith simplePrinters++viewDocWith :: Printers -> Doc -> IO ()+viewDocWith pr msg = do+ isTerminal <- hIsTerminalDevice stdout+ if isTerminal && lengthGreaterThan (20 :: Int) (lines $ renderString msg)+ then do viewer <- get_viewer+ pipeDocToPager viewer [] pr msg+ `ortryrunning` pipeDocToPager "less" [] pr msg+ `ortryrunning` pipeDocToPager "more" [] pr msg+#ifdef WIN32+ `ortryrunning` pipeDocToPager "more.com" [] pr msg+#endif+ `ortryrunning` pipeDocToPager "" [] pr msg+ else pipeDocToPager "" [] pr msg+ return ()+ where lengthGreaterThan n _ | n <= 0 = True+ lengthGreaterThan _ [] = False+ lengthGreaterThan n (_:xs) = lengthGreaterThan (n-1) xs++pipeDocToPager :: String -> [String] -> Printers -> Doc -> IO ExitCode++pipeDocToPager "" _ pr inp = do+ putDocLnWith pr inp+ return ExitSuccess++pipeDocToPager c args pr inp = withoutNonBlock $ withoutProgress $ do+ tmp <- tempdir_loc+ bracket (openBinaryTempFile tmp "darcs-pager") cleanup $ \(fn,fh) ->+ do hPutDocWith pr fh inp+ hClose fh+ bracket (openBinaryFile fn ReadMode) hClose $ \h ->+ do x <- do pid <- runProcess c args Nothing Nothing (Just h) Nothing Nothing+ waitForProcess pid+ when (x == ExitFailure 127) $+ putStrLn $ "Command not found:\n "++ show (c:args)+ return x+ where+ cleanup (f,h) = do try $ hClose h+ removeFileMayNotExist f
@@ -0,0 +1,83 @@+-- Copyright (C) 2005 David Roundy+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.+++{-# OPTIONS_GHC -cpp #-}+{-# LANGUAGE CPP #-}++module Darcs.FilePathMonad ( FilePathMonad, withFilePaths ) where++import Control.Monad ( MonadPlus, mplus, mzero )+import Data.Maybe ( catMaybes )++import Darcs.IO ( ReadableDirectory(..), WriteableDirectory(..) )+import Darcs.Patch.FileName ( FileName, fp2fn, fn2fp, super_name, break_on_dir,+ norm_path, movedirfilename )+#include "impossible.h"++data FilePathMonad a = FPM ([FileName] -> ([FileName], a))++withFilePaths :: [FilePath] -> FilePathMonad a -> [FilePath]+withFilePaths fps (FPM x) = map fn2fp $ fst $ x $ map fp2fn fps++instance Functor FilePathMonad where+ fmap f m = m >>= return . f++instance Monad FilePathMonad where+ (FPM x) >>= y = FPM z where z fs = case x fs of+ (fs', a) -> case y a of+ FPM yf -> yf fs'+ return x = FPM $ \fs -> (fs, x)++instance MonadPlus FilePathMonad where+ mzero = fail "mzero FilePathMonad" -- yuck!+ a `mplus` _ = a++instance ReadableDirectory FilePathMonad where+ -- We can't check it actually is a directory here+ mDoesDirectoryExist d =+ FPM $ \fs -> (fs, norm_path d `elem` map norm_path fs)+ -- We can't check it actually is a file here+ mDoesFileExist f =+ FPM $ \fs -> (fs, norm_path f `elem` map norm_path fs)+ mInCurrentDirectory d (FPM j) =+ FPM $ \fs -> (fs, snd $ j $ catMaybes $ map indir fs)+ where indir f = do (d',f') <- break_on_dir f+ if d == d' then Just f'+ else Nothing+ mGetDirectoryContents =+ FPM $ \fs -> (fs, filter (\f -> fp2fn "." == super_name f) fs)+ mReadFilePS = bug "can't mReadFilePS in FilePathMonad!"++instance WriteableDirectory FilePathMonad where+ mWithCurrentDirectory d (FPM j) =+ FPM $ \fs ->+ let splitfs = map splitf fs+ others = catMaybes $ map snd splitfs+ (myfs, a) = j $ catMaybes $ map fst splitfs+ splitf f = case break_on_dir f of+ Just (d', f') | d' == d -> (Just f', Nothing)+ _ -> (Nothing, Just f)+ in (others ++ myfs, a)+ mSetFileExecutable _ _ = return ()+ mWriteFilePS _ _ = return ()+ mCreateDirectory _ = return ()+ mRemoveFile f = FPM $ \fs -> (filter (/= f) fs, ())+ mRemoveDirectory f = FPM $ \fs -> (filter (/= f) fs, ())+ mRename a b = FPM $ \fs -> (map (movedirfilename a b) fs, ())+ mModifyFilePS _ _ = return ()+ mModifyFilePSs _ _ = return ()
@@ -0,0 +1,116 @@+-- Copyright (C) 2002-2004 David Roundy+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.++module Darcs.Flags ( DarcsFlag( .. ), Compression( .. ), compression, want_external_merge, isInteractive,+ maxCount,+ ) where+import Darcs.Patch.MatchData ( PatchMatch )+import Darcs.RepoPath ( AbsolutePath, AbsolutePathOrStd )++-- | The 'DarcsFlag' type is a list of all flags that can ever be+-- passed to darcs, or to one of its commands.+data DarcsFlag = Help | ListOptions | NoTest | Test+ | OnlyChangesToFiles+ | LeaveTestDir | NoLeaveTestDir+ | Timings | Debug | DebugVerbose | DebugHTTP+ | Verbose | NormalVerbosity | Quiet+ | Target String | Cc String+ | Output AbsolutePathOrStd | OutputAutoName AbsolutePath+ | Subject String | InReplyTo String+ | SendmailCmd String | Author String | PatchName String+ | OnePatch String | SeveralPatch String+ | AfterPatch String | UpToPatch String+ | TagName String | LastN Int | MaxCount Int | PatchIndexRange Int Int+ | NumberPatches+ | OneTag String | AfterTag String | UpToTag String+ | Context AbsolutePath | Count+ | LogFile AbsolutePath | RmLogFile+ | DistName String | All+ | Recursive | NoRecursive | Reorder+ | RestrictPaths | DontRestrictPaths+ | AskDeps | NoAskDeps | IgnoreTimes | LookForAdds | NoLookForAdds+ | AnyOrder | CreatorHash String+ | Intersection | Union | Complement+ | Sign | SignAs String | NoSign | SignSSL String+ | HappyForwarding+ | Verify AbsolutePath | VerifySSL AbsolutePath+ | SSHControlMaster | NoSSHControlMaster+ | EditDescription | NoEditDescription+ | Toks String+ | EditLongComment | NoEditLongComment | PromptLongComment+ | AllowConflicts | MarkConflicts | NoAllowConflicts+ | Boring | AllowCaseOnly | AllowWindowsReserved+ | DontGrabDeps | DontPromptForDependencies | PromptForDependencies+ | Compress | NoCompress | UnCompress+ | WorkRepoDir String | WorkRepoUrl String | RemoteRepo String+ | NewRepo String+ | Reply String | ApplyAs String+ | MachineReadable | HumanReadable+ | Pipe | Interactive+ | DiffCmd String+ | ExternalMerge String | Summary | NoSummary+ | Unified | Reverse+ | CheckPoint | Partial | Complete | Lazy | Ephemeral+ | FixFilePath AbsolutePath AbsolutePath | DiffFlags String+ | XMLOutput+ | ForceReplace+ | OnePattern PatchMatch | SeveralPattern PatchMatch+ | AfterPattern PatchMatch | UpToPattern PatchMatch+ | NonApply | NonVerify | NonForce+ | DryRun | SetDefault | NoSetDefault+ | FancyMoveAdd | NoFancyMoveAdd+ | Disable | SetScriptsExecutable | DontSetScriptsExecutable+ | UseHashedInventory | UseOldFashionedInventory+ | UseFormat2+ | PristinePlain | PristineNone | NoUpdateWorking+ | Sibling AbsolutePath | Relink | RelinkPristine | NoLinks+ | Files | NoFiles | Directories | NoDirectories+ | Pending | NoPending+ | PosthookCmd String | NoPosthook | AskPosthook | RunPosthook+ | PrehookCmd String | NoPrehook | AskPrehook | RunPrehook+ | UMask String+ | StoreInMemory+ | HTTPPipelining | NoHTTPPipelining+ | NoCache+ | AllowUnrelatedRepos+ | Check | Repair+ | NullFlag+ deriving ( Eq, Show )++data Compression = NoCompression | GzipCompression+compression :: [DarcsFlag] -> Compression+compression f | NoCompress `elem` f = NoCompression+ | otherwise = GzipCompression++want_external_merge :: [DarcsFlag] -> Maybe String+want_external_merge [] = Nothing+want_external_merge (ExternalMerge c:_) = Just c+want_external_merge (_:fs) = want_external_merge fs++isInteractive :: [DarcsFlag] -> Bool+isInteractive = isInteractive_ True+ where+ isInteractive_ def [] = def+ isInteractive_ _ (Interactive:_) = True+ isInteractive_ _ (All:_) = False+ isInteractive_ _ (DryRun:fs) = isInteractive_ False fs+ isInteractive_ def (_:fs) = isInteractive_ def fs++maxCount :: [DarcsFlag] -> Maybe Int+maxCount (MaxCount n : _) = Just n+maxCount (_:xs) = maxCount xs+maxCount [] = Nothing
@@ -0,0 +1,158 @@+-- Copyright (C) 2005 Tomasz Zielonka+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.++-- | This was originally Tomasz Zielonka's AtExit module, slightly generalised+-- to include global variables. Here, we attempt to cover broad, global+-- features, such as exit handlers. These features slightly break the Haskellian+-- purity of darcs, in favour of programming convenience.+module Darcs.Global ( atexit, with_atexit,+ sshControlMasterDisabled, setSshControlMasterDisabled,+ verboseMode, setVerboseMode,+ timingsMode, setTimingsMode,+ whenDebugMode, withDebugMode, setDebugMode,+ debugMessage, debugFail, putTiming,+#ifdef HAVE_HASKELL_ZLIB+ addCRCWarning, getCRCWarnings, resetCRCWarnings,+#endif+ darcsdir+ ) where++import Control.Monad ( when )+import Control.Concurrent.MVar+import Control.Exception (bracket_, catch, block, unblock)+import Data.IORef ( IORef, newIORef, readIORef, writeIORef )+#ifdef HAVE_HASKELL_ZLIB+import Data.IORef ( modifyIORef )+#endif+import System.IO.Unsafe (unsafePerformIO)+import System.IO (hPutStrLn, hPutStr, stderr)+import System.Time ( calendarTimeToString, toCalendarTime, getClockTime )+import Prelude hiding (catch)++{-# NOINLINE atexit_actions #-}+atexit_actions :: MVar (Maybe [IO ()])+atexit_actions = unsafePerformIO (newMVar (Just []))++-- | Registers an IO action to run just before darcs exits. Useful+-- for removing temporary files and directories, for example.+atexit :: IO () -> IO ()+atexit action = do+ modifyMVar_ atexit_actions $ \ml -> do+ case ml of+ Just l -> do+ return (Just (action : l))+ Nothing -> do+ hPutStrLn stderr "It's too late to use atexit"+ return Nothing++with_atexit :: IO a -> IO a+with_atexit prog = do+ bracket_+ (return ())+ exit+ prog+ where+ exit = block $ do+ Just actions <- swapMVar atexit_actions Nothing+ -- from now on atexit will not register new actions+ mapM_ runAction actions+ runAction action = do+ catch (unblock action) $ \exn -> do+ hPutStrLn stderr $ "Exception thrown by an atexit registered action:"+ hPutStrLn stderr $ show exn+++-- Write-once-read-many global variables make it easier to implement flags, such+-- as --no-ssh-cm. Using global variables reduces the number of parameters+-- that we have to pass around, but it is rather unsafe and should be used sparingly.++{-# NOINLINE _debugMode #-}+_debugMode :: IORef Bool+_debugMode = unsafePerformIO $ newIORef False++setDebugMode :: IO ()+setDebugMode = writeIORef _debugMode True++whenDebugMode :: IO () -> IO ()+whenDebugMode j = do b <- readIORef _debugMode+ when b j++withDebugMode :: (Bool -> IO a) -> IO a+withDebugMode j = readIORef _debugMode >>= j+++debugMessage :: String -> IO ()+debugMessage m = whenDebugMode $ do putTiming; hPutStrLn stderr m++debugFail :: String -> IO a+debugFail m = debugMessage m >> fail m++putTiming :: IO ()+putTiming = when timingsMode $ do t <- getClockTime >>= toCalendarTime+ hPutStr stderr (calendarTimeToString t++": ")++{-# NOINLINE _timingsMode #-}+_timingsMode :: IORef Bool+_timingsMode = unsafePerformIO $ newIORef False++setTimingsMode :: IO ()+setTimingsMode = writeIORef _timingsMode True++{-# NOINLINE timingsMode #-}+timingsMode :: Bool+timingsMode = unsafePerformIO $ readIORef _timingsMode++{-# NOINLINE _verboseMode #-}+_verboseMode :: IORef Bool+_verboseMode = unsafePerformIO $ newIORef False++setVerboseMode :: IO ()+setVerboseMode = writeIORef _verboseMode True++{-# NOINLINE verboseMode #-}+verboseMode :: Bool+verboseMode = unsafePerformIO $ readIORef _verboseMode++{-# NOINLINE _sshControlMasterDisabled #-}+_sshControlMasterDisabled :: IORef Bool+_sshControlMasterDisabled = unsafePerformIO $ newIORef False++setSshControlMasterDisabled :: IO ()+setSshControlMasterDisabled = writeIORef _sshControlMasterDisabled True++{-# NOINLINE sshControlMasterDisabled #-}+sshControlMasterDisabled :: Bool+sshControlMasterDisabled = unsafePerformIO $ readIORef _sshControlMasterDisabled++#ifdef HAVE_HASKELL_ZLIB+type CRCWarningList = [FilePath]+{-# NOINLINE _crcWarningList #-}+_crcWarningList :: IORef CRCWarningList+_crcWarningList = unsafePerformIO $ newIORef []++addCRCWarning :: FilePath -> IO ()+addCRCWarning fp = modifyIORef _crcWarningList (fp:)++getCRCWarnings :: IO [FilePath]+getCRCWarnings = readIORef _crcWarningList++resetCRCWarnings :: IO ()+resetCRCWarnings = writeIORef _crcWarningList []+#endif++darcsdir :: String+darcsdir = "_darcs"
@@ -0,0 +1,242 @@+{-# LANGUAGE CPP, FlexibleInstances #-}+{-# OPTIONS_GHC -cpp -fglasgow-exts #-}++-- Copyright (C) 2009 Petr Rockai+--+-- Permission is hereby granted, free of charge, to any person+-- obtaining a copy of this software and associated documentation+-- files (the "Software"), to deal in the Software without+-- restriction, including without limitation the rights to use, copy,+-- modify, merge, publish, distribute, sublicense, and/or sell copies+-- of the Software, and to permit persons to whom the Software is+-- furnished to do so, subject to the following conditions:+--+-- The above copyright notice and this permission notice shall be+-- included in all copies or substantial portions of the Software.+--+-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+-- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+-- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND+-- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS+-- BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN+-- ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN+-- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+-- SOFTWARE.++#include "gadts.h"++module Darcs.Gorsvet where++import Prelude hiding ( read, filter, writeFile, readFile )++-- darcs stuff+import ByteStringUtils( is_funky )+import Darcs.Repository ( Repository, slurp_pending )+import Darcs.Repository.Internal ( read_pending )+import Darcs.External ( backupByCopying )+import Darcs.Patch ( RepoPatch, Prim, hunk, canonize, binary, apply+ , sort_coalesceFL, addfile, rmfile, adddir, rmdir, invert)+import Darcs.Ordered ( FL(..), (+>+) )+import Darcs.Repository.Prefs ( filetype_function, FileType(..) )+import Darcs.IO+import Darcs.Sealed ( Sealed(Sealed), seal )+import Darcs.Patch( apply_to_filepaths )+import Darcs.Patch.TouchesFiles ( choose_touching )+import Darcs.Patch.FileName ( fn2fp )++import qualified Data.ByteString.Lazy.Char8 as BL+import qualified Data.ByteString.Char8 as BS+import Control.Monad.State.Strict+import System.Directory( removeFile, doesFileExist )+import Data.Maybe+import Data.List( union )++import Darcs.Arguments ( DarcsFlag( LookForAdds, IgnoreTimes ) )+import Darcs.RepoPath ( SubPath, sp2fn )++import Text.Regex( matchRegex )+import Darcs.Repository.Prefs( boring_regexps )++import Storage.Hashed+import Storage.Hashed.Tree+import qualified Storage.Hashed.Index as I+import Storage.Hashed.AnchoredPath+import Storage.Hashed.Darcs( darcsFormatHash, darcsTreeHash )+import Storage.Hashed.Monad+ ( virtualTreeIO, hashedTreeIO, plainTreeIO+ , unlink, rename, createDirectory, writeFile+ , readFile -- ratify readFile: haskell_policy je natvrdlá+ , cwd, tree, TreeIO )+import Storage.Hashed++floatFn = floatPath . fn2fp++instance ReadableDirectory TreeIO where+ mDoesDirectoryExist d = gets (\x -> isJust $ findTree (tree x) (floatFn d))+ mDoesFileExist f = gets (\x -> isJust $ findFile (tree x) (floatFn f))+ mInCurrentDirectory d action = do -- TODO bracket?+ wd <- gets cwd+ modify (\x -> x { cwd = floatFn d })+ x <- action+ modify (\x -> x { cwd = wd })+ return x+ mGetDirectoryContents = error "get dir contents"+ mReadFilePS p = do x <- readFile (floatFn p) -- ratify readFile: ...+ return $ BS.concat (BL.toChunks x)++instance WriteableDirectory TreeIO where+ mWithCurrentDirectory = mInCurrentDirectory+ mSetFileExecutable _ _ = return ()+ mWriteFilePS p ps = writeFile -- ratify readFile: haskell_policy is stupid.+ (floatFn p) (BL.fromChunks [ps])+ mCreateDirectory p = createDirectory (floatFn p)+ mRename from to = rename (floatFn from) (floatFn to)+ mRemoveDirectory = unlink . floatFn+ mRemoveFile = unlink . floatFn++treeDiff :: (FilePath -> FileType) -> Tree -> Tree -> IO (FL Prim C(x y))+#ifdef GADT_WITNESSES+treeDiff = undefined -- Sigh.+#else+treeDiff ft t1 t2 = do+ (from, to) <- diffTrees t1 t2+ diffs <- sequence $ zipTrees diff from to+ return $ foldr (+>+) NilFL diffs+ where diff :: AnchoredPath -> Maybe TreeItem -> Maybe TreeItem+ -> IO (FL Prim)+ diff _ (Just (SubTree _)) (Just (SubTree _)) = return NilFL+ diff p (Just (SubTree _)) Nothing =+ return $ rmdir (anchorPath "" p) :>: NilFL+ diff p Nothing (Just (SubTree _)) =+ return $ adddir (anchorPath "" p) :>: NilFL+ diff p Nothing b'@(Just (File _)) =+ do diff <- diff p (Just (File emptyBlob)) b'+ return $ addfile (anchorPath "" p) :>: diff+ diff p a'@(Just (File _)) Nothing =+ do diff <- diff p a' (Just (File emptyBlob))+ return $ diff +>+ (rmfile (anchorPath "" p) :>: NilFL)+ diff p (Just (File a')) (Just (File b')) =+ do a <- read a'+ b <- read b'+ let path = anchorPath "" p+ case ft path of+ TextFile | no_bin a && no_bin b ->+ return $ text_diff path a b+ _ -> return $ if a /= b+ then binary path (strict a) (strict b) :>: NilFL+ else NilFL+ diff p _ _ = fail $ "Missing case at path " ++ show p+ text_diff p a b+ | BL.null a && BL.null b = NilFL+ | BL.null a = diff_from_empty p b+ | BL.null b = diff_to_empty p a+ | otherwise = line_diff p (lines a) (lines b)+ line_diff p a b = canonize (hunk p 1 a b)+ diff_to_empty p x | BL.last x == '\n' = line_diff p (init $ lines x) []+ | otherwise = line_diff p (lines x) [BS.empty]+ diff_from_empty p x = invert (diff_to_empty p x)+ no_bin = not . is_funky . strict . BL.take 4096+ lines = map strict . BL.split '\n'+ strict = BS.concat . BL.toChunks+#endif++readRecordedAndPending :: (RepoPatch p) => Repository p C(r u t) -> IO Tree+readRecordedAndPending repo = do+ pristine <- readDarcsPristine "."+ Sealed pending <- pendingChanges repo []+ (_, t) <- virtualTreeIO (apply [] pending) pristine+ return t++pendingChanges :: (RepoPatch p) => Repository p C(r u t)+ -> [SubPath] -> IO (Sealed (FL Prim C(r)))+pendingChanges repo paths = do+ slurp_pending repo -- XXX: only here to get us the "pending conflicts" check+ -- that I don't know yet how to implement properly+ Sealed pending <- read_pending repo+ let files = map (fn2fp . sp2fn) paths+ pre_files = apply_to_filepaths (invert pending) files+ relevant = case paths of+ [] -> seal pending+ _ -> choose_touching pre_files pending+ return relevant++readIndex :: IO Tree+readIndex = I.readIndex "_darcs/index" darcsTreeHash++unrecordedChanges :: (RepoPatch p) => [DarcsFlag] -> Repository p C(r u t)+ -> [SubPath] -> IO (FL Prim C(r y))+unrecordedChanges opts repo paths = do+ checkIndex repo+ pristine <- readDarcsPristine "."+ Sealed pending <- pendingChanges repo paths+ (_, current') <- virtualTreeIO (apply [] pending) pristine+ relevant <- restrictSubpaths repo paths+ nonboring <- restrictBoring++ let current = relevant current'+ working <- case (LookForAdds `elem` opts, IgnoreTimes `elem` opts) of+ (False, False) -> do+ all <- readIndex+ expand (relevant all)+ (False, True) -> do+ guide <- expand current+ all <- readPlainTree "."+ return $ relevant $ (restrict guide) all+ (True, _) -> do+ all <- readPlainTree "."+ return $ relevant $ nonboring all++ ft <- filetype_function+ diff <- treeDiff ft current working+ return $ sort_coalesceFL (pending +>+ diff)++applyToTentativePristine _ patches =+ do pristine <- readDarcsPristine "."+ (_, tree) <- hashedTreeIO (apply [] patches)+ pristine "_darcs/pristine.hashed"+ BS.writeFile "_darcs/tentative_pristine" $+ BS.concat [BS.pack "pristine:"+ , darcsFormatHash (fromJust $ treeHash tree)]++applyToWorking :: (RepoPatch p) => Repository p C(r u t)+ -> Sealed (FL Prim C(u)) -> IO Tree+applyToWorking _ (Sealed patches) =+ do working <- readIndex+ snd `fmap` plainTreeIO (apply [] patches) working "."++filter_paths files =+ \p _ -> any (\x -> x `isPrefix` p || p `isPrefix` x) files++restrict_paths files = if null files+ then id+ else filter $ filter_paths files++restrict_subpaths = restrict_paths . map (floatPath . fn2fp . sp2fn)++restrictSubpaths repo subpaths = do+ Sealed pending <- read_pending repo+ let paths = map (fn2fp . sp2fn) subpaths+ paths' = paths `union` apply_to_filepaths pending paths+ anchored = map floatPath paths'+ return $ restrict_paths anchored++restrictBoring = do+ boring <- boring_regexps+ let boring' (AnchoredPath (Name x:_)) _ | x == BS.pack "_darcs" = False+ boring' p _ = not $ any (\rx -> isJust $ matchRegex rx p') boring+ where p' = anchorPath "" p+ return $ filter boring'++checkIndex repo = do+ invalid <- doesFileExist "_darcs/index_invalid"+ exist <- doesFileExist "_darcs/index"+ when (not exist || invalid) $ updateIndex repo+ when invalid $ removeFile "_darcs/index_invalid"++updateIndex repo = do+ pristine <- readRecordedAndPending repo >>= expand+ I.updateIndexFrom "_darcs/index" darcsTreeHash pristine >>= expand+ return ()++invalidateIndex _ = do+ BS.writeFile "_darcs/index_invalid" BS.empty
@@ -0,0 +1,185 @@+-- Copyright (C) 2006 David Roundy+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.++{-# OPTIONS_GHC -cpp #-}+{-# LANGUAGE CPP #-}++#include "gadts.h"++module Darcs.Hopefully ( Hopefully, PatchInfoAnd,+ piap, n2pia, patchInfoAndPatch,+ conscientiously, hopefully, info,+ hopefullyM, createHashed, extractHash,+ actually, unavailable ) where++import System.IO.Unsafe ( unsafeInterleaveIO )++import Darcs.SignalHandler ( catchNonSignal )+import Printer ( Doc, renderString, errorDoc, text, ($$) )+import Darcs.Patch.Info ( PatchInfo, human_friendly, idpatchinfo )+import Darcs.Patch ( RepoPatch, Named, patch2patchinfo )+import Darcs.Patch.Prim ( Effect(..), Conflict(..) )+import Darcs.Patch.Patchy ( Patchy, ReadPatch(..), Apply(..), Invert(..),+ ShowPatch(..), Commute(..) )+import Darcs.Ordered ( MyEq, unsafeCompare, (:>)(..), (:\/:)(..), (:/\:)(..) )+import Darcs.Sealed ( Sealed(Sealed), seal, mapSeal )+import Darcs.Utils ( prettyException )++-- | @'Hopefully' p C@ @(x y)@ is @'Either' String (p C@ @(x y))@ in a+-- form adapted to darcs patches. The @C@ @(x y)@ represents the type+-- witness for the patch that should be there. The @Hopefully@ type+-- just tells whether we expect the patch to be hashed or not, and+-- 'SimpleHopefully' does the real work of emulating+-- 'Either'. @Hopefully sh@ represents an expected unhashed patch, and+-- @Hashed hash sh@ represents an expected hashed patch with its hash.+data Hopefully a C(x y) = Hopefully (SimpleHopefully a C(x y)) | Hashed String (SimpleHopefully a C(x y))++-- | @SimpleHopefully@ is a variant of @Either String@ adapted for+-- type witnesses. @Actually@ is the equivalent of @Right@, while+-- @Unavailable@ is @Left@.+data SimpleHopefully a C(x y) = Actually (a C(x y)) | Unavailable String++-- | @'PatchInfoAnd' p C(a b)@ represents a hope we have to get a+-- patch through its info. We're not sure we have the patch, but we+-- know its info.+data PatchInfoAnd p C(a b) = PIAP !PatchInfo (Hopefully (Named p) C(a b))++fmapH :: (a C(x y) -> b C(w z)) -> Hopefully a C(x y) -> Hopefully b C(w z)+fmapH f (Hopefully sh) = Hopefully (ff sh)+ where ff (Actually a) = Actually (f a)+ ff (Unavailable e) = Unavailable e+fmapH f (Hashed h sh) = Hashed h (ff sh)+ where ff (Actually a) = Actually (f a)+ ff (Unavailable e) = Unavailable e++info :: PatchInfoAnd p C(a b) -> PatchInfo+info (PIAP i _) = i++-- | @'piap' i p@ creates a PatchInfoAnd containing p with info i.+piap :: PatchInfo -> Named p C(a b) -> PatchInfoAnd p C(a b)+piap i p = PIAP i (Hopefully $ Actually p)++-- | @n2pia@ creates a PatchInfoAnd represeting a @Named@ patch.+n2pia :: Named p C(x y) -> PatchInfoAnd p C(x y)+n2pia x = patch2patchinfo x `piap` x++patchInfoAndPatch :: PatchInfo -> Hopefully (Named p) C(a b) -> PatchInfoAnd p C(a b)+patchInfoAndPatch = PIAP++-- | @'hopefully' hp@ tries to get a patch from a 'PatchInfoAnd'+-- value. If it fails, it outputs an error \"failed to read patch:+-- \<description of the patch>\". We get the description of the patch+-- from the info part of 'hp'+hopefully :: PatchInfoAnd p C(a b) -> Named p C(a b)+hopefully = conscientiously $ \e -> text "failed to read patch:" $$ e++-- | @'conscientiously' er hp@ tries to extract a patch from a 'PatchInfoAnd'.+-- If it fails, it applies the error handling function @er@ to a description+-- of the patch info component of @hp@.+conscientiously :: (Doc -> Doc)+ -> PatchInfoAnd p C(a b) -> Named p C(a b)+conscientiously er (PIAP pinf hp) =+ case hopefully2either hp of+ Right p -> p+ Left e -> errorDoc $ er (human_friendly pinf $$ text e)++-- | @hopefullyM@ is a version of @hopefully@ which calls @fail@ in a+-- monad instead of erroring.+hopefullyM :: Monad m => PatchInfoAnd p C(a b) -> m (Named p C(a b))+hopefullyM (PIAP pinf hp) = case hopefully2either hp of+ Right p -> return p+ Left e -> fail $ renderString (human_friendly pinf $$ text e)++-- Any recommendations for a nice adverb to name the below?+hopefully2either :: Hopefully a C(x y) -> Either String (a C(x y))+hopefully2either (Hopefully (Actually p)) = Right p+hopefully2either (Hashed _ (Actually p)) = Right p+hopefully2either (Hopefully (Unavailable e)) = Left e+hopefully2either (Hashed _ (Unavailable e)) = Left e++actually :: a C(x y) -> Hopefully a C(x y)+actually = Hopefully . Actually++createHashed :: String -> (String -> IO (Sealed (a C(x)))) -> IO (Sealed (Hopefully a C(x)))+createHashed h f = do mapSeal (Hashed h) `fmap` unsafeInterleaveIO (f' `catchNonSignal` handler)+ where+ f' = do Sealed x <- f h+ return (Sealed (Actually x))+ handler e = return $ seal $ Unavailable $ prettyException e++extractHash :: PatchInfoAnd p C(a b) -> Either (Named p C(a b)) String+extractHash (PIAP _ (Hashed s _)) = Right s+extractHash hp = Left $ conscientiously (\e -> text "unable to read patch:" $$ e) hp++unavailable :: String -> Hopefully a C(x y)+unavailable = Hopefully . Unavailable++instance MyEq p => MyEq (PatchInfoAnd p) where+ unsafeCompare (PIAP i _) (PIAP i2 _) = i == i2++--instance Invert (p C(x y)) => Invert (PatchInfoAnd (p C(x y))) where+instance Invert p => Invert (PatchInfoAnd p) where+ identity = PIAP idpatchinfo (actually identity)+ invert (PIAP i p) = PIAP i (invert `fmapH` p)++instance (Conflict p, Effect p, ShowPatch p) => ShowPatch (PatchInfoAnd p) where+ showPatch (PIAP n p) = case hopefully2either p of+ Right x -> showPatch x+ Left _ -> human_friendly n+ showContextPatch s (PIAP n p) = case hopefully2either p of+ Right x -> showContextPatch s x+ Left _ -> human_friendly n+ description (PIAP n _) = human_friendly n+ summary (PIAP n p) = case hopefully2either p of+ Right x -> summary x+ Left _ -> human_friendly n+ showNicely (PIAP n p) = case hopefully2either p of+ Right x -> showNicely x+ Left _ -> human_friendly n++instance Commute p => Commute (PatchInfoAnd p) where+ commute (x :> y) = do y' :> x' <- commute (hopefully x :> hopefully y)+ return $ (info y `piap` y') :> (info x `piap` x')+ list_touched_files = list_touched_files . hopefully+ merge (x :\/: y) = case merge (hopefully x :\/: hopefully y) of+ y' :/\: x' -> (info y `piap` y') :/\: (info x `piap` x')++instance Apply p => Apply (PatchInfoAnd p) where+ apply opts p = apply opts $ hopefully p+ applyAndTryToFix p = do mp' <- applyAndTryToFix $ hopefully p+ case mp' of+ Nothing -> return Nothing+ Just (e,p') -> return $ Just (e, n2pia p')++instance ReadPatch p => ReadPatch (PatchInfoAnd p) where+ readPatch' wanteof = do x <- readPatch' wanteof+ case x of+ Just (Sealed p) -> return $ Just $ Sealed $ n2pia p+ Nothing -> return Nothing++instance Effect p => Effect (PatchInfoAnd p) where+ effect = effect . hopefully+ effectRL = effectRL . hopefully++instance Conflict p => Conflict (PatchInfoAnd p) where+ list_conflicted_files = list_conflicted_files . hopefully+ resolve_conflicts = resolve_conflicts . hopefully+ commute_no_conflicts (x:>y) = do y':>x' <- commute_no_conflicts (hopefully x :> hopefully y)+ return (info y `piap` y' :> info x `piap` x')+ conflictedEffect = conflictedEffect . hopefully++instance RepoPatch p => Patchy (PatchInfoAnd p)
@@ -0,0 +1,249 @@+-- Copyright (C) 2005 David Roundy+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.++{-# OPTIONS_GHC -fglasgow-exts #-}+module Darcs.IO ( ReadableDirectory(..), WriteableDirectory(..),+ TolerantIO, runTolerantly, runSilently,+ ) where++import Prelude hiding ( catch )+import Data.Char ( toLower )+import Data.List ( isSuffixOf )+import System.IO.Error ( isDoesNotExistError, isPermissionError )+import Control.Exception ( catch, catchJust, ioErrors )+import Control.Monad.Error+import System.Directory ( getDirectoryContents, createDirectory,+ removeDirectory, removeFile,+ renameFile, renameDirectory,+ doesDirectoryExist, doesFileExist,+ )++import ByteStringUtils ( linesPS, unlinesPS)+import qualified Data.ByteString as B (ByteString, empty, null, readFile)+import qualified Data.ByteString.Char8 as BC (unpack, pack)++import Darcs.Utils ( withCurrentDirectory, prettyException )+import Darcs.External ( backupByCopying, backupByRenaming )+import Printer ( Doc, renderPS )+import Darcs.Patch.FileName ( FileName, fn2fp, fp2fn )+import Darcs.Lock ( writeBinFile, readBinFile, writeAtomicFilePS )+import Workaround ( setExecutable )++class (Functor m, MonadPlus m) => ReadableDirectory m where+ mDoesDirectoryExist :: FileName -> m Bool+ mDoesFileExist :: FileName -> m Bool+ mInCurrentDirectory :: FileName -> m a -> m a+ mGetDirectoryContents :: m [FileName]+ mReadBinFile :: FileName -> m String+ mReadBinFile f = liftM BC.unpack $ mReadFilePS f+ mReadFilePS :: FileName -> m B.ByteString+ mReadFilePSs :: FileName -> m [B.ByteString]+ mReadFilePSs f = linesPS `liftM` mReadFilePS f++class ReadableDirectory m => WriteableDirectory m where+ mWithCurrentDirectory :: FileName -> m a -> m a+ mSetFileExecutable :: FileName -> Bool -> m ()+ mWriteBinFile :: FileName -> String -> m ()+ mWriteBinFile fn s = mWriteFilePS fn $ BC.pack s+ mWriteFilePS :: FileName -> B.ByteString -> m ()+ mWriteFilePSs :: FileName -> [B.ByteString] -> m ()+ mWriteFilePSs f ss = mWriteFilePS f (unlinesPS ss)+ mCreateDirectory :: FileName -> m ()+ mRemoveDirectory :: FileName -> m ()+ mWriteDoc :: FileName -> Doc -> m ()+ mWriteDoc f d = mWriteFilePS f (renderPS d)+ mCreateFile :: FileName -> m ()+ mCreateFile f = mWriteFilePS f B.empty+ mRemoveFile :: FileName -> m ()+ mRename :: FileName -> FileName -> m ()+ mModifyFilePS :: FileName -> (B.ByteString -> m B.ByteString) -> m ()+ mModifyFilePS f j = do ps <- mReadFilePS f+ ps' <- j ps+ mWriteFilePS f ps'+ mModifyFilePSs :: FileName -> ([B.ByteString] -> m [B.ByteString]) -> m ()+ mModifyFilePSs f j = do ps <- mReadFilePSs f+ ps' <- j ps+ mWriteFilePSs f ps'++instance ReadableDirectory IO where+ mDoesDirectoryExist = doesDirectoryExist . fn2fp+ mDoesFileExist = doesFileExist . fn2fp+ mInCurrentDirectory = withCurrentDirectory . fn2fp+ mGetDirectoryContents = map fp2fn `liftM` getDirectoryContents "."+ mReadBinFile = readBinFile . fn2fp+ mReadFilePS = B.readFile . fn2fp++instance WriteableDirectory IO where+ mWithCurrentDirectory = mInCurrentDirectory+ mSetFileExecutable = setExecutable . fn2fp+ mWriteBinFile = writeBinFile . fn2fp+ mWriteFilePS = writeAtomicFilePS . fn2fp+ mCreateDirectory = createDirectory . fn2fp+ mCreateFile f = do exf <- mDoesFileExist f+ if exf then fail $ "File '"++fn2fp f++"' already exists!"+ else do exd <- mDoesDirectoryExist f+ if exd then fail $ "File '"++fn2fp f++"' already exists!"+ else mWriteFilePS f B.empty+ mRemoveFile f = do let fp = fn2fp f+ x <- B.readFile fp+ when (not $ B.null x) $+ fail $ "Cannot remove non-empty file "++fp+ removeFile fp+ mRemoveDirectory = removeDirectory . fn2fp+ mRename a b = catchJust ioErrors+ (renameDirectory x y `mplus` renameFile x y)+ -- We need to catch does not exist errors, since older+ -- versions of darcs allowed users to rename nonexistent+ -- files. :(+ (\e -> if isDoesNotExistError e+ then return ()+ else ioError e)+ where x = fn2fp a+ y = fn2fp b++class Monad m => TolerantMonad m where+ warning :: IO () -> m ()+ runIO :: m a -> IO a+ runTM :: IO a -> m a++newtype TolerantIO a = TIO { runTolerantly :: IO a }+instance TolerantMonad TolerantIO where+ warning io = TIO $ io `catch` \e -> putStrLn $ "Warning: " ++ prettyException e+ runIO (TIO io) = io+ runTM io = TIO io++newtype SilentIO a = SIO { runSilently :: IO a }+instance TolerantMonad SilentIO where+ warning io = SIO $ io `catch` \_ -> return ()+ runIO (SIO io) = io+ runTM io = SIO io++-- NOTE: The following instance declarations are duplicated merely to avoid+-- enabling -fallow-undecidable-instances. If we used+-- -fallow-undecidable-instances, we would write instead:++-- instance TolerantMonad m => Monad m where+-- ...++-- etc.+instance Functor TolerantIO where+ fmap f m = m >>= return . f++instance Monad TolerantIO where+ f >>= g = runTM $ runIO f >>= runIO . g+ f >> g = runTM $ runIO f >> runIO g+ fail s = runTM $ fail s+ return x = runTM $ return x++instance Functor SilentIO where+ fmap f m = m >>= return . f++instance Monad SilentIO where+ f >>= g = runTM $ runIO f >>= runIO . g+ f >> g = runTM $ runIO f >> runIO g+ fail s = runTM $ fail s+ return x = runTM $ return x++instance MonadPlus TolerantIO where+ mzero = runTM mzero+ mplus a b = runTM (mplus (runIO a) (runIO b))+instance MonadPlus SilentIO where+ mzero = runTM mzero+ mplus a b = runTM (mplus (runIO a) (runIO b))++instance ReadableDirectory TolerantIO where+ mDoesDirectoryExist d = runTM $ mDoesDirectoryExist d+ mDoesFileExist f = runTM $ mDoesFileExist f+ mInCurrentDirectory i j = runTM $ mInCurrentDirectory i (runIO j)+ mGetDirectoryContents = runTM mGetDirectoryContents+ mReadBinFile f = runTM $ mReadBinFile f+ mReadFilePS f = runTM $ mReadFilePS f+instance ReadableDirectory SilentIO where+ mDoesDirectoryExist d = runTM $ mDoesDirectoryExist d+ mDoesFileExist f = runTM $ mDoesFileExist f+ mInCurrentDirectory i j = runTM $ mInCurrentDirectory i (runIO j)+ mGetDirectoryContents = runTM mGetDirectoryContents+ mReadBinFile f = runTM $ mReadBinFile f+ mReadFilePS f = runTM $ mReadFilePS f++instance WriteableDirectory TolerantIO where+ mWithCurrentDirectory = mInCurrentDirectory+ mSetFileExecutable f e = warning $ mSetFileExecutable f e+ mWriteBinFile f s = warning $ mWriteBinFile f s+ mWriteFilePS f s = warning $ mWriteFilePS f s+ mCreateFile f = warning $ backup f >> mWriteFilePS f B.empty+ mCreateDirectory d = warning $ backup d >> mCreateDirectory d+ mRemoveFile f = warning $ mRemoveFile f+ mRemoveDirectory d = warning $ catchJust ioErrors+ (mRemoveDirectory d)+ (\e ->+ if "(Directory not empty)" `isSuffixOf` show e+ then ioError $ userError $+ "Not deleting " ++ fn2fp d ++ " because it is not empty."+ else ioError $ userError $+ "Not deleting " ++ fn2fp d ++ " because:\n" ++ show e)+ mRename a b = warning $ catchJust ioErrors+ (let do_backup = if (map toLower x == map toLower y)+ then backupByCopying y -- avoid making the original vanish+ else backupByRenaming y+ in do_backup >> mRename a b)+ (\e -> case () of+ _ | isPermissionError e -> ioError $ userError $+ couldNotRename ++ "."+ | isDoesNotExistError e -> ioError $ userError $+ couldNotRename ++ " because " ++ x ++ " does not exist."+ | otherwise -> ioError e+ )+ where+ x = fn2fp a+ y = fn2fp b+ couldNotRename = "Could not rename " ++ x ++ " to " ++ y+instance WriteableDirectory SilentIO where+ mWithCurrentDirectory = mInCurrentDirectory+ mSetFileExecutable f e = warning $ mSetFileExecutable f e+ mWriteBinFile f s = warning $ mWriteBinFile f s+ mWriteFilePS f s = warning $ mWriteFilePS f s+ mCreateFile f = warning $ backup f >> mWriteFilePS f B.empty+ mCreateDirectory d = warning $ backup d >> mCreateDirectory d+ mRemoveFile f = warning $ mRemoveFile f+ mRemoveDirectory d = warning $ catchJust ioErrors+ (mRemoveDirectory d)+ (\e ->+ if "(Directory not empty)" `isSuffixOf` show e+ then ioError $ userError $+ "Not deleting " ++ fn2fp d ++ " because it is not empty."+ else ioError $ userError $+ "Not deleting " ++ fn2fp d ++ " because:\n" ++ show e)+ mRename a b = warning $ catchJust ioErrors+ (let do_backup = if (map toLower x == map toLower y)+ then backupByCopying y -- avoid making the original vanish+ else backupByRenaming y+ in do_backup >> mRename a b)+ (\e -> case () of+ _ | isPermissionError e -> ioError $ userError $+ couldNotRename ++ "."+ | isDoesNotExistError e -> ioError $ userError $+ couldNotRename ++ " because " ++ x ++ " does not exist."+ | otherwise -> ioError e+ )+ where+ x = fn2fp a+ y = fn2fp b+ couldNotRename = "Could not rename " ++ x ++ " to " ++ y++backup :: FileName -> IO ()+backup f = backupByRenaming $ fn2fp f
@@ -0,0 +1,333 @@+-- Copyright (C) 2003 David Roundy+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.++{-# LANGUAGE CPP, ForeignFunctionInterface #-}++module Darcs.Lock ( withLock, withLockCanFail,+ withTemp, withOpenTemp, withStdoutTemp,+ withTempDir, withPermDir, withDelayedDir, withNamedTemp,+ writeToFile, appendToFile,+ writeBinFile, writeDocBinFile, appendBinFile, appendDocBinFile,+ readBinFile, readDocBinFile,+ writeAtomicFilePS,+ gzWriteAtomicFilePS, gzWriteAtomicFilePSs, gzWriteDocFile,+ rm_recursive, removeFileMayNotExist,+ canonFilename, maybeRelink,+ world_readable_temp, tempdir_loc,+ environmentHelpTmpdir, environmentHelpKeepTmpdir+ ) where++import Prelude hiding ( catch )+import Data.List ( inits )+import Data.Maybe ( isJust, listToMaybe )+import System.Exit ( exitWith, ExitCode(..) )+import System.IO ( openBinaryFile, openBinaryTempFile,+ hClose, hPutStr, Handle,+ IOMode(WriteMode, AppendMode), hFlush, stdout )+import System.IO.Error ( isDoesNotExistError, isAlreadyExistsError )+import Control.Exception ( bracket, catchJust, ioErrors, throwIO,+ Exception(IOException), catch, try )+import System.Directory ( removeFile, removeDirectory,+ doesFileExist, doesDirectoryExist,+ getDirectoryContents, createDirectory,+ getTemporaryDirectory,+ )+import System.FilePath.Posix ( splitDirectories )+import Workaround ( renameFile )+import Darcs.Utils ( withCurrentDirectory, maybeGetEnv, firstJustIO )+import Control.Monad ( unless, when )++import Darcs.URL ( is_relative )+import Darcs.Utils ( catchall, add_to_error_loc )+import Darcs.RepoPath ( AbsolutePath, FilePathLike, toFilePath,+ getCurrentDirectory, setCurrentDirectory )++import ByteStringUtils ( gzWriteFilePSs)+import qualified Data.ByteString as B (null, readFile, hPut, ByteString)+import qualified Data.ByteString.Char8 as BC (unpack)++import Darcs.SignalHandler ( withSignalsBlocked )+import Printer ( Doc, hPutDoc, packedString, empty, renderPSs )+import Darcs.Global ( atexit, darcsdir )+import Darcs.Compat ( mk_stdout_temp, canonFilename, maybeRelink,+ atomic_create, sloppy_atomic_create )+import System.Posix.Files ( getSymbolicLinkStatus, isDirectory,+ fileMode, getFileStatus, setFileMode )+import System.Posix ( sleep )+#include "impossible.h"++withLock :: String -> IO a -> IO a+releaseLock :: String -> IO ()++withLock s job = bracket (getlock s 30) releaseLock (\_ -> job)++-- | Tries to perform some task if it can obtain the lock,+-- Otherwise, just gives up without doing the task+withLockCanFail :: String -> IO a -> IO (Either () a)+withLockCanFail s job =+ bracket (takeLock s)+ (\l -> if l then releaseLock s else return ())+ (\l -> if l then job >>= (return.Right)+ else return $ Left ())++getlock :: String -> Int -> IO String+getlock l 0 = do putStrLn $ "Couldn't get lock "++l+ exitWith $ ExitFailure 1+getlock lbad tl = do l <- canonFilename lbad+ gotit <- takeLock l+ if gotit then return l+ else do putStrLn $ "Waiting for lock "++l+ hFlush stdout -- for Windows+ done <- sleep 2+ if done == 0+ then getlock l (tl - 1)+ else getlock l 0++removeFileMayNotExist :: FilePathLike p => p -> IO ()+removeFileMayNotExist f = catchNonExistence (removeFile $ toFilePath f) ()++catchNonExistence :: IO a -> a -> IO a+catchNonExistence job nonexistval =+ catchJust ioErrors job $+ \e -> if isDoesNotExistError e then return nonexistval+ else ioError e++releaseLock s = removeFileMayNotExist s++takeLock :: FilePathLike p => p -> IO Bool+takeLock fp =+ do atomic_create $ toFilePath fp+ return True+ `catch` \e -> case e of+ IOException e'+ | isAlreadyExistsError e' ->+ return False+ _ -> do pwd <- getCurrentDirectory+ throwIO $ add_to_error_loc e+ ("takeLock "++toFilePath fp++" in "++toFilePath pwd)++takeFile :: FilePath -> IO Bool+takeFile fp =+ do sloppy_atomic_create fp+ return True+ `catch` \e -> case e of+ IOException e'+ | isAlreadyExistsError e' ->+ return False+ _ -> do pwd <- getCurrentDirectory+ throwIO $ add_to_error_loc e+ ("takeFile "++fp++" in "++toFilePath pwd)++-- |'withTemp' safely creates an empty file (not open for writing) and+-- returns its name.+--+-- The temp file operations are rather similar to the locking operations, in+-- that they both should always try to clean up, so exitWith causes trouble.+withTemp :: (String -> IO a) -> IO a+withTemp = bracket get_empty_file removeFileMayNotExist+ where get_empty_file = do (f,h) <- openBinaryTempFile "." "darcs"+ hClose h+ return f++-- |'withOpenTemp' creates an already open temporary+-- file. Both of them run their argument and then delete the file. Also,+-- both of them (to my knowledge) are not susceptible to race conditions on+-- the temporary file (as long as you never delete the temporary file; that+-- would reintroduce a race condition).+withOpenTemp :: ((Handle, String) -> IO a) -> IO a+withOpenTemp = bracket get_empty_file cleanup+ where cleanup (h,f) = do try $ hClose h+ removeFileMayNotExist f+ get_empty_file = invert `fmap` openBinaryTempFile "." "darcs"+ invert (a,b) = (b,a)++withStdoutTemp :: (String -> IO a) -> IO a+withStdoutTemp = bracket (mk_stdout_temp "stdout_") removeFileMayNotExist++tempdir_loc :: IO FilePath+tempdir_loc = firstJustIO [ readBinFile (darcsdir++"/prefs/tmpdir") >>= return . Just . head.words >>= chkdir,+ maybeGetEnv "DARCS_TMPDIR" >>= chkdir,+ getTemporaryDirectory >>= chkdir . Just,+ getCurrentDirectorySansDarcs,+ return $ Just "." -- always returns a Just+ ]+ >>= return . fromJust+ where chkdir Nothing = return Nothing+ chkdir (Just d) = doesDirectoryExist d >>= return . \e -> if e then Just (d++"/") else Nothing++environmentHelpTmpdir :: ([String], [String])+environmentHelpTmpdir = (["DARCS_TMPDIR", "TMPDIR"], [+ "Darcs often creates temporary directories. For example, the `darcs",+ "diff' command creates two for the working trees to be diffed. By",+ "default temporary directories are created in /tmp, or if that doesn't",+ "exist, in _darcs (within the current repo). This can be overridden by",+ "specifying some other directory in the file _darcs/prefs/tmpdir or the",+ "environment variable $DARCS_TMPDIR or $TMPDIR."])++getCurrentDirectorySansDarcs :: IO (Maybe FilePath)+getCurrentDirectorySansDarcs = do+ c <- getCurrentDirectory+ return $ listToMaybe $ drop 5 $ reverse $ takeWhile no_darcs $ inits $ toFilePath c+ where no_darcs x = not $ darcsdir `elem` splitDirectories x++data WithDirKind = Perm | Temp | Delayed++withDir :: WithDirKind -> String -> (AbsolutePath -> IO a) -> IO a+withDir _ "" _ = bug "withDir called with empty directory name"+withDir kind abs_or_relative_name job = do+ absolute_name <- if is_relative abs_or_relative_name+ then fmap (++ abs_or_relative_name) tempdir_loc+ else return abs_or_relative_name+ formerdir <- getCurrentDirectory+ bracket (create_directory absolute_name 0)+ (\dir -> do setCurrentDirectory formerdir+ k <- keep_tmpdir+ unless k $ do case kind of+ Perm -> return ()+ Temp -> rm_recursive (toFilePath dir)+ Delayed -> atexit $ rm_recursive (toFilePath dir))+ job+ where newname name 0 = name+ newname name n = name ++ "-" ++ show n+ create_directory :: FilePath -> Int -> IO AbsolutePath+ create_directory name n+ = do createDirectory $ newname name n+ setCurrentDirectory $ newname name n+ getCurrentDirectory+ `catch` (\e -> case e of+ IOException e'+ | isAlreadyExistsError e' ->+ create_directory name (n+1)+ _ -> throwIO e)+ keep_tmpdir = isJust `fmap` maybeGetEnv "DARCS_KEEP_TMPDIR"++environmentHelpKeepTmpdir :: ([String], [String])+environmentHelpKeepTmpdir = (["DARCS_KEEP_TMPDIR"],[+ "If the environment variable DARCS_KEEP_TMPDIR is defined, darcs will",+ "not remove the temporary directories it creates. This is intended",+ "primarily for debugging Darcs itself, but it can also be useful, for",+ "example, to determine why your test preference (see `darcs setpref')",+ "is failing when you run `darcs record', but working when run manually."])++-- |'withPermDir' is like 'withTempDir', except that it doesn't+-- delete the directory afterwards.+withPermDir :: String -> (AbsolutePath -> IO a) -> IO a+withPermDir = withDir Perm++-- |'withTempDir' creates an empty directory and then removes it when it+-- is no longer needed. withTempDir creates a temporary directory. The+-- location of that directory is determined by the contents of+-- _darcs/prefs/tmpdir, if it exists, otherwise by @$DARCS_TMPDIR@, and if+-- that doesn't exist then whatever your operating system considers to be a+-- a temporary directory (e.g. @$TMPDIR@ under Unix, @$TEMP@ under+-- Windows).+--+-- If none of those exist it creates the temporary directory+-- in the current directory, unless the current directory is under a _darcs+-- directory, in which case the temporary directory in the parent of the highest+-- _darcs directory to avoid accidentally corrupting darcs's internals.+-- This should not fail, but if it does indeed fail, we go ahead and use the+-- current directory anyway. If @$DARCS_KEEP_TMPDIR@ variable is set+-- temporary directory is not removed, this can be useful for debugging.+withTempDir :: String -> (AbsolutePath -> IO a) -> IO a+withTempDir = withDir Temp++withDelayedDir :: String -> (AbsolutePath -> IO a) -> IO a+withDelayedDir = withDir Delayed++doesDirectoryReallyExist :: FilePath -> IO Bool+doesDirectoryReallyExist f =+ catchNonExistence (isDirectory `fmap` getSymbolicLinkStatus f) False++rm_recursive :: FilePath -> IO ()+rm_recursive d =+ do isd <- doesDirectoryReallyExist d+ if not isd+ then removeFile d+ else when isd $ do conts <- actual_dir_contents+ withCurrentDirectory d $+ mapM_ rm_recursive conts+ removeDirectory d+ where actual_dir_contents = -- doesn't include . or ..+ do c <- getDirectoryContents d+ return $ filter (/=".") $ filter (/="..") c++world_readable_temp :: String -> IO String+world_readable_temp f = wrt 0+ where wrt :: Int -> IO String+ wrt 100 = fail $ "Failure creating temp named "++f+ wrt n = let f_new = f++"-"++show n+ in do ok <- takeFile f_new+ if ok then do atexit $ removeFileMayNotExist f_new+ return f_new+ else wrt (n+1)++withNamedTemp :: String -> (String -> IO a) -> IO a+withNamedTemp n = bracket get_empty_file removeFileMayNotExist+ where get_empty_file = world_readable_temp n++readBinFile :: FilePathLike p => p -> IO String+readBinFile = fmap BC.unpack . B.readFile . toFilePath++readDocBinFile :: FilePathLike p => p -> IO Doc+readDocBinFile fp = do ps <- B.readFile $ toFilePath fp+ return $ if B.null ps then empty else packedString ps++appendBinFile :: FilePathLike p => p -> String -> IO ()+appendBinFile f s = appendToFile f $ \h -> hPutStr h s++appendDocBinFile :: FilePathLike p => p -> Doc -> IO ()+appendDocBinFile f d = appendToFile f $ \h -> hPutDoc h d++writeBinFile :: FilePathLike p => p -> String -> IO ()+writeBinFile f s = writeToFile f $ \h -> hPutStr h s++writeDocBinFile :: FilePathLike p => p -> Doc -> IO ()+writeDocBinFile f d = writeToFile f $ \h -> hPutDoc h d++writeAtomicFilePS :: FilePathLike p => p -> B.ByteString -> IO ()+writeAtomicFilePS f ps = writeToFile f $ \h -> B.hPut h ps++gzWriteAtomicFilePS :: FilePathLike p => p -> B.ByteString -> IO ()+gzWriteAtomicFilePS f ps = gzWriteAtomicFilePSs f [ps]++gzWriteAtomicFilePSs :: FilePathLike p => p -> [B.ByteString] -> IO ()+gzWriteAtomicFilePSs f pss =+ withSignalsBlocked $ withNamedTemp (toFilePath f) $ \newf -> do+ gzWriteFilePSs newf pss+ already_exists <- doesFileExist $ toFilePath f+ when already_exists $ do mode <- fileMode `fmap` getFileStatus (toFilePath f)+ setFileMode newf mode+ `catchall` return ()+ renameFile newf (toFilePath f)++gzWriteDocFile :: FilePathLike p => p -> Doc -> IO ()+gzWriteDocFile f d = gzWriteAtomicFilePSs f $ renderPSs d++writeToFile :: FilePathLike p => p -> (Handle -> IO ()) -> IO ()+writeToFile f job =+ withSignalsBlocked $ withNamedTemp (toFilePath f) $ \newf -> do+ bracket (openBinaryFile newf WriteMode) hClose job+ already_exists <- doesFileExist (toFilePath f)+ when already_exists $ do mode <- fileMode `fmap` getFileStatus (toFilePath f)+ setFileMode newf mode+ `catchall` return ()+ renameFile newf (toFilePath f)++appendToFile :: FilePathLike p => p -> (Handle -> IO ()) -> IO ()+appendToFile f job = withSignalsBlocked $ + bracket (openBinaryFile (toFilePath f) AppendMode) hClose job
@@ -0,0 +1,568 @@+% Copyright (C) 2004-2005 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\begin{code}+{-# OPTIONS_GHC -cpp -fglasgow-exts #-}+{-# LANGUAGE CPP, ScopedTypeVariables, MultiParamTypeClasses, FlexibleInstances, Rank2Types #-}++#include "gadts.h"++module Darcs.Match ( match_first_patchset, match_second_patchset,+ match_patch,+ match_a_patch, match_a_patchread,+ get_first_match, get_nonrange_match,+ get_partial_first_match, get_partial_second_match,+ get_partial_nonrange_match,+ first_match, second_match, have_nonrange_match,+ have_patchset_match, get_one_patchset,+ checkMatchSyntax,+ ) where++import Text.Regex ( mkRegex, matchRegex )+import Control.Monad ( when )+import Data.Maybe ( isJust )++import Darcs.Hopefully ( PatchInfoAnd, info, piap,+ conscientiously, hopefully )+import Darcs.Patch.Info ( just_name )+import Darcs.Patch ( RepoPatch, Patch, Patchy, Named, invert, invertRL, patch2patchinfo, apply )+import Darcs.Repository ( Repository, PatchSet, SealedPatchSet, read_repo,+ slurp_recorded, createPristineDirectoryTree )+import Darcs.Repository.ApplyPatches ( apply_patches )+import Darcs.Patch.Depends ( get_patches_in_tag, get_patches_beyond_tag )+import Darcs.Ordered ( RL(..), concatRL, consRLSealed )++import ByteStringUtils ( mmapFilePS )+import qualified Data.ByteString as B (ByteString)++import Darcs.Flags ( DarcsFlag( OnePatch, SeveralPatch, Context,+ StoreInMemory,+ AfterPatch, UpToPatch, LastN, PatchIndexRange,+ OneTag, AfterTag, UpToTag,+ OnePattern, SeveralPattern,+ AfterPattern, UpToPattern ) )+import Darcs.Patch.Bundle ( scan_context )+import Darcs.Patch.Match ( Matcher, MatchFun, match_pattern, apply_matcher, make_matcher, parseMatch )+import Darcs.Patch.MatchData ( PatchMatch )+import Printer ( text, ($$) )++import Darcs.RepoPath ( toFilePath )+import Darcs.IO ( WriteableDirectory(..), ReadableDirectory(..) )+import Darcs.SlurpDirectory ( SlurpMonad, writeSlurpy, withSlurpy )+import Darcs.Patch.FileName ( FileName, super_name, norm_path, (///) )+import Darcs.Sealed ( FlippedSeal(..), Sealed2(..),+ seal, flipSeal, seal2, unsealFlipped, unseal2, unseal )+#include "impossible.h"+\end{code}++\paragraph{Selecting patches}\label{selecting}++Many commands operate on a patch or patches that have already been recorded.+There are a number of options that specify which patches are selected for+these operations: \verb!--patch!, \verb!--match!, \verb!--tag!, and variants+on these, which for \verb!--patch! are \verb!--patches!,+\verb!--from-patch!, and \verb!--to-patch!. The \verb!--patch! and+\verb!--tag! forms simply take (POSIX extended, aka \verb!egrep!) regular+expressions and match them against tag and patch names. \verb!--match!,+described below, allows more powerful patterns.++The plural forms of these options select all matching patches. The singular+forms select the last matching patch. The range (from and to) forms select+patches after or up to (both inclusive) the last matching patch.++These options use the current order of patches in the repository. darcs may+reorder patches, so this is not necessarily the order of creation or the+order in which patches were applied. However, as long as you are just+recording patches in your own repository, they will remain in order.++% NOTE --no-deps is implemented in SelectChanges.lhs, but documented here+% for concistency.+When a patch or a group of patches is selected, all patches they depend on+get silently selected too. For example: \verb!darcs pull --patches bugfix!+means ``pull all the patches with `bugfix' in their name, along with any+patches they require.'' If you really only want patches with `bugfix' in+their name, you should use the \verb!--no-deps! option, which makes darcs+exclude any matched patches from the selection which have dependencies that+are themselves not explicitly matched by the selection.++For \verb!unrecord!, \verb!unpull! and \verb!obliterate!, patches that+depend on the selected patches are silently included, or if+\verb!--no-deps! is used selected patches with dependencies on not selected+patches are excluded from the selection.++\begin{code}+data InclusiveOrExclusive = Inclusive | Exclusive deriving Eq++-- | @have_nonrange_match flags@ tells whether there is a flag in+-- @flags@ which corresponds to a match that is "non-range". Thus,+-- @--match@, @--patch@ and @--index@ make @have_nonrange_match@+-- true, but not @--from-patch@ or @--to-patch@.+have_nonrange_match :: [DarcsFlag] -> Bool+have_nonrange_match fs = isJust (has_index_range fs) || isJust (nonrange_matcher fs::Maybe (Matcher Patch))++-- | @have_patchset_match flags@ tells whether there is a "patchset+-- match" in the flag list. A patchset match is @--match@ or+-- @--patch@, or @--context@, but not @--from-patch@ nor (!)+-- @--index@.+-- Question: Is it supposed not to be a subset of @have_nonrange_match@?+have_patchset_match :: [DarcsFlag] -> Bool+have_patchset_match fs = isJust (nonrange_matcher fs::Maybe (Matcher Patch)) || hasC fs+ where hasC [] = False+ hasC (Context _:_) = True+ hasC (_:xs) = hasC xs++get_nonrange_match :: RepoPatch p => Repository p C(r u t) -> [DarcsFlag] -> IO ()+get_nonrange_match r fs = withRecordedMatchSmart fs r (get_nonrange_match_s fs)++get_partial_nonrange_match :: RepoPatch p => Repository p C(r u t)+ -> [DarcsFlag] -> [FileName] -> IO ()+get_partial_nonrange_match r fs files =+ withRecordedMatchOnlySomeSmart fs r files (get_nonrange_match_s fs)++get_nonrange_match_s :: (MatchMonad m p, RepoPatch p) =>+ [DarcsFlag] -> PatchSet p C(x) -> m ()+get_nonrange_match_s fs repo =+ case nonrange_matcher fs of+ Just m -> if nonrange_matcher_is_tag fs+ then get_tag_s m repo+ else get_matcher_s Exclusive m repo+ Nothing -> fail "Pattern not specified in get_nonrange_match."++-- | @first_match fs@ tells whether @fs@ implies a "first match", that+-- is if we match against patches from a point in the past on, rather+-- than against all patches since the creation of the repository.+first_match :: [DarcsFlag] -> Bool+first_match fs = isJust (has_lastn fs)+ || isJust (first_matcher fs::Maybe (Matcher Patch))+ || isJust (has_index_range fs)++get_first_match :: RepoPatch p => Repository p C(r u t) -> [DarcsFlag] -> IO ()+get_first_match r fs = withRecordedMatchSmart fs r (get_first_match_s fs)++get_partial_first_match :: RepoPatch p => Repository p C(r u t)+ -> [DarcsFlag] -> [FileName] -> IO ()+get_partial_first_match r fs files =+ withRecordedMatchOnlySomeSmart fs r files (get_first_match_s fs)++get_first_match_s :: (MatchMonad m p, RepoPatch p) =>+ [DarcsFlag] -> PatchSet p C(x) -> m ()+get_first_match_s fs repo =+ case has_lastn fs of+ Just n -> applyInvRL `unsealFlipped` (safetake n $ concatRL repo)+ Nothing -> case first_matcher fs of+ Nothing -> fail "Pattern not specified in get_first_match."+ Just m -> if first_matcher_is_tag fs+ then get_tag_s m repo+ else get_matcher_s Inclusive m repo+++-- | @second_match fs@ tells whether @fs@ implies a "second match", that+-- is if we match against patches up to a point in the past on, rather+-- than against all patches until now.+second_match :: [DarcsFlag] -> Bool+second_match fs = isJust (second_matcher fs::Maybe (Matcher Patch)) || isJust (has_index_range fs)++get_partial_second_match :: RepoPatch p => Repository p C(r u t)+ -> [DarcsFlag] -> [FileName] -> IO ()+get_partial_second_match r fs files =+ withRecordedMatchOnlySomeSmart fs r files $ \repo ->+ case second_matcher fs of+ Nothing -> fail "Two patterns not specified in get_second_match."+ Just m -> if second_matcher_is_tag fs+ then get_tag_s m repo+ else get_matcher_s Exclusive m repo++checkMatchSyntax :: [DarcsFlag] -> IO ()+checkMatchSyntax opts = do+ case get_match_pattern opts of+ Nothing -> return ()+ Just p -> either fail (const $ return ()) $ (parseMatch p::Either String (MatchFun Patch))++get_match_pattern :: [DarcsFlag] -> Maybe PatchMatch+get_match_pattern [] = Nothing+get_match_pattern (OnePattern m:_) = Just m+get_match_pattern (SeveralPattern m:_) = Just m+get_match_pattern (_:fs) = get_match_pattern fs++tagmatch :: String -> Matcher p+tagmatch r = make_matcher ("tag-name "++r) tm+ where tm (Sealed2 p) =+ let n = just_name (info p) in+ take 4 n == "TAG " && isJust (matchRegex (mkRegex r) $ drop 4 n)++mymatch :: String -> Matcher p+mymatch r = make_matcher ("patch-name "++r) mm+ where mm (Sealed2 p) = isJust . matchRegex (mkRegex r) . just_name . info $ p+++-- | strictJust is a strict version of the Just constructor, used to ensure+-- that if we claim we've got a pattern match, that the pattern will+-- actually match (rathern than fail to compile properly).+--+-- /First matcher, Second matcher and Nonrange matcher/+--+-- When we match for patches, we have a PatchSet, of which we want a+-- subset. This subset is formed by the patches in a given interval+-- which match a given criterion. If we represent time going left to+-- right (which means the 'PatchSet' is written right to left), then+-- we have (up to) three 'Matcher's: the 'nonrange_matcher' is the+-- criterion we use to select among patches in the interval, the+-- 'first_matcher' is the left bound of the interval, and the+-- 'last_matcher' is the right bound. Each of these matchers can be+-- present or not according to the options.+strictJust :: a -> Maybe a+strictJust x = Just $! x++-- | @nonrange_matcher@ is the criterion that is used to match against+-- patches in the interval. It is 'Just m' when the @--patch@, @--match@,+-- @--tag@ options are passed (or their plural variants).+nonrange_matcher :: Patchy p => [DarcsFlag] -> Maybe (Matcher p)+nonrange_matcher [] = Nothing+nonrange_matcher (OnePattern m:_) = strictJust $ match_pattern m+nonrange_matcher (OneTag t:_) = strictJust $ tagmatch t+nonrange_matcher (OnePatch p:_) = strictJust $ mymatch p+nonrange_matcher (SeveralPattern m:_) = strictJust $ match_pattern m+nonrange_matcher (SeveralPatch p:_) = strictJust $ mymatch p+nonrange_matcher (_:fs) = nonrange_matcher fs++-- | @nonrange_matcher_is_tag@ returns true if the matching option was+-- '--tag'+nonrange_matcher_is_tag :: [DarcsFlag] -> Bool+nonrange_matcher_is_tag [] = False+nonrange_matcher_is_tag (OneTag _:_) = True+nonrange_matcher_is_tag (_:fs) = nonrange_matcher_is_tag fs++-- | @first_matcher@ returns the left bound of the matched interval.+-- This left bound is also specified when we use the singular versions+-- of @--patch@, @--match@ and @--tag@. Otherwise, @first_matcher@+-- returns @Nothing@.+first_matcher :: Patchy p => [DarcsFlag] -> Maybe (Matcher p)+first_matcher [] = Nothing+first_matcher (OnePattern m:_) = strictJust $ match_pattern m+first_matcher (AfterPattern m:_) = strictJust $ match_pattern m+first_matcher (AfterTag t:_) = strictJust $ tagmatch t+first_matcher (OnePatch p:_) = strictJust $ mymatch p+first_matcher (AfterPatch p:_) = strictJust $ mymatch p+first_matcher (_:fs) = first_matcher fs++first_matcher_is_tag :: [DarcsFlag] -> Bool+first_matcher_is_tag [] = False+first_matcher_is_tag (AfterTag _:_) = True+first_matcher_is_tag (_:fs) = first_matcher_is_tag fs++second_matcher :: Patchy p => [DarcsFlag] -> Maybe (Matcher p)+second_matcher [] = Nothing+second_matcher (OnePattern m:_) = strictJust $ match_pattern m+second_matcher (UpToPattern m:_) = strictJust $ match_pattern m+second_matcher (OnePatch p:_) = strictJust $ mymatch p+second_matcher (UpToPatch p:_) = strictJust $ mymatch p+second_matcher (UpToTag t:_) = strictJust $ tagmatch t+second_matcher (_:fs) = second_matcher fs++second_matcher_is_tag :: [DarcsFlag] -> Bool+second_matcher_is_tag [] = False+second_matcher_is_tag (UpToTag _:_) = True+second_matcher_is_tag (_:fs) = second_matcher_is_tag fs++-- | @match_a_patchread fs p@ tells whether @p@ matches the matchers in+-- the flags listed in @fs@.+match_a_patchread :: Patchy p => [DarcsFlag] -> PatchInfoAnd p C(x y) -> Bool+match_a_patchread fs = case nonrange_matcher fs of+ Nothing -> const True+ Just m -> apply_matcher m++-- | @match_a_patch fs p@ tells whether @p@ matches the matchers in+-- the flags @fs@+match_a_patch :: Patchy p => [DarcsFlag] -> Named p C(x y) -> Bool+match_a_patch fs p =+ case nonrange_matcher fs of+ Nothing -> True+ Just m -> apply_matcher m (patch2patchinfo p `piap` p)++match_patch :: RepoPatch p => [DarcsFlag] -> PatchSet p C(x) -> Sealed2 (Named p)+match_patch fs ps =+ case has_index_range fs of+ Just (a,a') | a == a' -> case (unseal myhead) $ dropn (a-1) ps of+ Just (Sealed2 p) -> seal2 $ hopefully p+ Nothing -> error "Patch out of range!"+ | otherwise -> bug ("Invalid index range match given to match_patch: "+++ show (PatchIndexRange a a'))+ where myhead :: PatchSet p C(x) -> Maybe (Sealed2 (PatchInfoAnd p))+ myhead (NilRL:<:x) = myhead x+ myhead ((x:<:_):<:_) = Just $ seal2 x+ myhead NilRL = Nothing+ Nothing -> case nonrange_matcher fs of+ Nothing -> bug "Couldn't match_patch"+ Just m -> find_a_patch m ps++get_one_patchset :: RepoPatch p => Repository p C(r u t) -> [DarcsFlag] -> IO (SealedPatchSet p)+get_one_patchset repository fs =+ case nonrange_matcher fs of+ Just m -> do ps <- read_repo repository+ if nonrange_matcher_is_tag fs+ then return $ get_matching_tag m ps+ else return $ match_a_patchset m ps+ Nothing -> (seal . scan_context) `fmap` mmapFilePS (toFilePath $ context_f fs)+ where context_f [] = bug "Couldn't match_nonrange_patchset"+ context_f (Context f:_) = f+ context_f (_:xs) = context_f xs++-- | @has_lastn fs@ return the @--last@ argument in @fs@, if any.+has_lastn :: [DarcsFlag] -> Maybe Int+has_lastn [] = Nothing+has_lastn (LastN (-1):_) = error "--last requires a positive integer argument."+has_lastn (LastN n:_) = Just n+has_lastn (_:fs) = has_lastn fs++has_index_range :: [DarcsFlag] -> Maybe (Int,Int)+has_index_range [] = Nothing+has_index_range (PatchIndexRange x y:_) = Just (x,y)+has_index_range (_:fs) = has_index_range fs++-- | @match_first_patchset fs ps@ returns the part of @ps@ before its+-- first matcher, ie the one that comes first dependencywise. Hence,+-- patches in @match_first_patchset fs ps@ are the ones we don't want.+--+-- Question: are they really? Florent+match_first_patchset :: RepoPatch p => [DarcsFlag] -> PatchSet p C(x) -> SealedPatchSet p+match_first_patchset fs patchset =+ case has_lastn fs of+ Just n -> dropn n patchset+ Nothing ->+ case has_index_range fs of+ Just (_,b) -> dropn b patchset+ Nothing ->+ case first_matcher fs of+ Nothing -> bug "Couldn't match_first_patchset"+ Just m -> unseal (dropn 1) $ if first_matcher_is_tag fs+ then get_matching_tag m patchset+ else match_a_patchset m patchset++-- | @dropn n ps@ drops the @n@ last patches from @ps@.+dropn :: Int -> PatchSet p C(x) -> SealedPatchSet p+dropn n ps | n <= 0 = seal ps+dropn n (NilRL:<:ps) = dropn n ps+dropn _ NilRL = seal $ NilRL:<:NilRL+dropn n ((_:<:ps):<:xs) = dropn (n-1) $ ps:<:xs++-- | @match_second_patchset fs ps@ returns the part of @ps@ before its+-- second matcher, ie the one that comes last dependencywise.+match_second_patchset :: RepoPatch p => [DarcsFlag] -> PatchSet p C(x) -> SealedPatchSet p+match_second_patchset fs ps =+ case has_index_range fs of+ Just (a,_) -> dropn (a-1) ps+ Nothing ->+ case second_matcher fs of+ Nothing -> bug "Couldn't match_second_patchset"+ Just m -> if second_matcher_is_tag fs+ then get_matching_tag m ps+ else match_a_patchset m ps++-- | @find_a_patch m ps@ returns the last patch in @ps@ matching @m@, and+-- calls 'error' if there is none.+find_a_patch :: RepoPatch p => Matcher p -> PatchSet p C(x) -> Sealed2 (Named p)+find_a_patch m NilRL = error $ "Couldn't find patch matching " ++ show m+find_a_patch m (NilRL:<:xs) = find_a_patch m xs+find_a_patch m ((p:<:ps):<:xs) | apply_matcher m p = seal2 $ hopefully p+ | otherwise = find_a_patch m (ps:<:xs)++-- | @match_a_patchset m ps@ returns a (the largest?) subset of @ps@+-- ending in patch which matches @m@. Calls 'error' if there is none.+match_a_patchset :: RepoPatch p => Matcher p -> PatchSet p C(x) -> SealedPatchSet p+match_a_patchset m NilRL = error $ "Couldn't find patch matching " ++ show m+match_a_patchset m (NilRL:<:xs) = match_a_patchset m xs+match_a_patchset m ((p:<:ps):<:xs) | apply_matcher m p = seal ((p:<:ps):<:xs)+ | otherwise = match_a_patchset m (ps:<:xs)++-- | @get_matching_tag m ps@, where @m@ is a 'Matcher' which matches tags+-- returns a 'SealedPatchSet' containing all patches in the last tag which+-- matches @m@. Last tag means the most recent tag in repository order,+-- i.e. the last one you'd see if you ran darcs changes -t @m@. Calls+-- 'error' if there is no matching tag.+get_matching_tag :: RepoPatch p => Matcher p -> PatchSet p C(x) -> SealedPatchSet p+get_matching_tag m NilRL = error $ "Couldn't find a tag matching " ++ show m+get_matching_tag m (NilRL:<:xs) = get_matching_tag m xs+get_matching_tag m xxx@((p:<:ps):<:xs)+ | apply_matcher m p = get_patches_in_tag (info p) xxx+ | otherwise = get_matching_tag m (ps:<:xs)++-- | @match_exists m ps@ tells whether there is a patch matching+-- @m@ in @ps@+match_exists :: Matcher p -> PatchSet p C(x) -> Bool+match_exists _ NilRL = False+match_exists m (NilRL:<:xs) = match_exists m xs+match_exists m ((p:<:ps):<:xs) | apply_matcher m $ p = True+ | otherwise = match_exists m (ps:<:xs)++apply_inv_to_matcher :: (RepoPatch p, WriteableDirectory m) => InclusiveOrExclusive -> Matcher p -> PatchSet p C(x) -> m ()+apply_inv_to_matcher _ _ NilRL = impossible+apply_inv_to_matcher ioe m (NilRL:<:xs) = apply_inv_to_matcher ioe m xs+apply_inv_to_matcher ioe m ((p:<:ps):<:xs)+ | apply_matcher m p = when (ioe == Inclusive) (apply_invp p)+ | otherwise = apply_invp p >> apply_inv_to_matcher ioe m (ps:<:xs)++-- | @maybe_read_file@ recursively gets the contents of all files+-- in a directory, or just the contents of a file if called on a+-- simple file.+maybe_read_file :: ReadableDirectory m => FileName -> m ([(FileName, B.ByteString)])+maybe_read_file file = do+ d <- mDoesDirectoryExist file+ if d+ then do+ children <- mInCurrentDirectory file mGetDirectoryContents+ maybe_read_files [file /// ch | ch <- children]+ else do+ e <- mDoesFileExist file+ if e+ then do+ contents <- mReadFilePS file+ return [(norm_path file, contents)]+ else return []+ where maybe_read_files [] = return []+ maybe_read_files (f:fs) = do+ x <- maybe_read_file f+ y <- maybe_read_files fs+ return $ concat [x,y]++get_matcher_s :: (MatchMonad m p, RepoPatch p) =>+ InclusiveOrExclusive -> Matcher p -> PatchSet p C(x) -> m ()+get_matcher_s ioe m repo =+ if match_exists m repo+ then apply_inv_to_matcher ioe m repo+ else fail $ "Couldn't match pattern "++ show m++get_tag_s :: (MatchMonad m p, RepoPatch p) =>+ Matcher p -> PatchSet p C(x) -> m ()+get_tag_s match repo = do+ let pinfo = patch2patchinfo `unseal2` (find_a_patch match repo)+ case get_patches_beyond_tag pinfo repo of+ FlippedSeal (extras:<:NilRL) -> applyInvRL $ extras+ _ -> impossible++-- | @apply_invp@ tries to get the patch that's in a 'PatchInfoAnd+-- patch', and to apply its inverse. If we fail to fetch the patch+-- (presumably in a partial repositiory), then we share our sorrow+-- with the user.+apply_invp :: (Patchy p, WriteableDirectory m) => PatchInfoAnd p C(x y) -> m ()+apply_invp hp = apply [] (invert $ fromHopefully hp)+ where fromHopefully = conscientiously $ \e ->+ text "Sorry, partial repository problem. Patch not available:"+ $$ e+ $$ text ""+ $$ text "If you think what you're trying to do is ok then"+ $$ text "report this as a bug on the darcs-user list."++-- | a version of 'take' for 'RL' lists that cater for contexts.+safetake :: Int -> RL a C(x y) -> FlippedSeal (RL a) C(y)+safetake 0 _ = flipSeal NilRL+safetake _ NilRL = error "There aren't that many patches..."+safetake i (a:<:as) = a `consRLSealed` safetake (i-1) as++-- | A @MatchMonad p m@ is a monad in which we match patches from @p@+-- by playing with them in @m@, a 'WriteableDirectory' monad. How we+-- play with the patches depends on the instance of @MatchMonad@ we're+-- using. If we use @IO@, then we'll apply the patches directly in+-- @m@, if we use @SlurpMonad@, then we'll apply the patches to a+-- slurpy, and write to disk at the end. Note that both @IO@ and+-- @SlurpMonad@ have an instance of 'WriteableDirectory' that+-- implicitely writes in the current directory.+class (RepoPatch p, WriteableDirectory m) => MatchMonad m p where+ withRecordedMatch :: Repository p C(r u t)+ -> (PatchSet p C(r) -> m ()) -> IO ()+ -- ^ @withRecordedMatch@ is responsible for getting the recorded state+ -- into the monad, and then applying the second argument, and+ -- finally placing the resulting state into the current directory.+ withRecordedMatchOnlySomeFiles+ :: Repository p C(r u t) -> [FileName]+ -> (PatchSet p C(r) -> m ()) -> IO ()+ -- ^ @withRecordedMatchOnlySomeFiles@ is a variant of+ -- withRecordedMatch that may only return some of the files+ -- (e.g. if we want to run diff on just a few files).+ withRecordedMatchOnlySomeFiles r _ j = withRecordedMatch r j+ applyInvRL :: RL (PatchInfoAnd p) C(x r) -> m ()+ applyInvRL NilRL = return ()+ applyInvRL (p:<:ps) = apply_invp p >> applyInvRL ps++withRecordedMatchIO :: RepoPatch p => Repository p C(r u t)+ -> (PatchSet p C(r) -> IO ()) -> IO ()+withRecordedMatchIO = withRecordedMatch++-- | @withRecordedMatchSmart@ hides away the choice of the+-- 'SlurpMonad' to use in order to apply 'withRecordedMatch'.+-- If we have the @--store-in-memory@ flag, then use 'SlurpMonad', else+-- use @IO@. In both case, the result is in the @IO@ monad.+--+-- Suggestion: shouldn't we name @withRecordedMatchSmart@+-- @withRecordedMatch@, and give the monad function another name such+-- as @withRecordedMatchRaw@?+withRecordedMatchSmart :: RepoPatch p => [DarcsFlag] -> Repository p C(r u t)+ -> (forall m. MatchMonad m p => PatchSet p C(r) -> m ())+ -> IO ()+withRecordedMatchSmart opts r j =+ do if StoreInMemory `elem` opts then withSM r j+ else withRecordedMatchIO r j+ where withSM :: RepoPatch p => Repository p C(r u t)+ -> (PatchSet p C(r) -> SlurpMonad ()) -> IO ()+ withSM = withRecordedMatch++-- | @withRecordedMatchOnlySomeSmart@ is the smart version of+-- 'withRecordedMatchOnlySome'. It runs 'withRecordedMatchOnlySome'+-- either in the 'SlurpMonad' or in @IO@ according to the+-- @--store-in-memory@ flag.+withRecordedMatchOnlySomeSmart :: RepoPatch p => [DarcsFlag] -> Repository p C(r u t)+ -> [FileName]+ -> (forall m. MatchMonad m p => PatchSet p C(r) -> m ())+ -> IO ()+withRecordedMatchOnlySomeSmart opts r [] j = withRecordedMatchSmart opts r j+withRecordedMatchOnlySomeSmart opts r files j =+ do if StoreInMemory `elem` opts then withSM r files j+ else withIO r files j+ where withSM :: RepoPatch p => Repository p C(r u t) -> [FileName]+ -> (PatchSet p C(r) -> SlurpMonad ()) -> IO ()+ withSM = withRecordedMatchOnlySomeFiles+ withIO :: RepoPatch p => Repository p C(r u t) -> [FileName]+ -> (PatchSet p C(r) -> IO ()) -> IO ()+ withIO = withRecordedMatchOnlySomeFiles++instance RepoPatch p => MatchMonad IO p where+ withRecordedMatch r job = do createPristineDirectoryTree r "."+ read_repo r >>= job+ applyInvRL = apply_patches [] . invertRL -- this gives nicer feedback++instance RepoPatch p => MatchMonad SlurpMonad p where+ withRecordedMatch r job =+ do ps <- read_repo r+ s <- slurp_recorded r+ case withSlurpy s (job ps) of+ Left err -> fail err+ Right (s',_) -> writeSlurpy s' "."+ withRecordedMatchOnlySomeFiles r fs job =+ do ps <- read_repo r+ s <- slurp_recorded r+ case withSlurpy s (job ps >> mapM maybe_read_file fs) of+ Left err -> fail err+ Right (_,fcs) -> mapM_ createAFile $ concat fcs+ where createAFile (p,c) = do ensureDirectories $ super_name p+ mWriteFilePS p c+ ensureDirectories d =+ do isPar <- mDoesDirectoryExist d+ if isPar+ then return ()+ else do ensureDirectories $ super_name d+ mCreateDirectory d++\end{code}
@@ -0,0 +1,277 @@+-- Copyright (C) 2007 David Roundy+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.++{-# OPTIONS_GHC -cpp -fglasgow-exts #-}+{-# LANGUAGE CPP #-}+-- , MagicHash, TypeOperators, GADTs #-}++#include "gadts.h"++module Darcs.Ordered ( EqCheck(..), isEq, (:>)(..), (:<)(..), (:\/:)(..), (:/\:)(..),+ FL(..), RL(..),Proof(..),+#ifndef GADT_WITNESSES+ unsafeUnFL, unsafeFL, unsafeRL, unsafeUnRL,+#endif+ lengthFL, mapFL, mapFL_FL, spanFL, foldlFL, allFL,+ splitAtFL, bunchFL, foldlRL,+ lengthRL, isShorterThanRL, mapRL, mapRL_RL, zipWithFL,+ unsafeMap_l2f, filterE, filterFL,+ reverseFL, reverseRL, (+>+), (+<+),+ nullFL, concatFL, concatRL, concatReverseFL, headRL,+ MyEq, unsafeCompare, (=\/=), (=/\=),+ consRLSealed, nullRL,+ unsafeCoerceP, unsafeCoerceP2+ ) where++#include "impossible.h"+import GHC.Base (unsafeCoerce#)+import Darcs.Show+import Darcs.Sealed ( FlippedSeal(..), flipSeal )++data EqCheck C(a b) where+ IsEq :: EqCheck C(a a)+ NotEq :: EqCheck C(a b)++instance Eq (EqCheck C(a b)) where+ IsEq == IsEq = True+ NotEq == NotEq = True+ _ == _ = False++isEq :: EqCheck C(a b) -> Bool+isEq IsEq = True+isEq NotEq = False++instance Show (EqCheck C(a b)) where+ show IsEq = "IsEq"+ show NotEq = "NotEq"++data Proof a C(x y) where+ Proof :: a -> Proof a C(x x)++data (a1 :> a2) C(x y) = FORALL(z) (a1 C(x z)) :> (a2 C(z y))+infixr 1 :>+data (a1 :< a2) C(x y) = FORALL(z) (a1 C(z y)) :< (a2 C(x z))+infix 1 :<+infix 1 :/\:, :\/:+data (a1 :\/: a2) C(x y) = FORALL(z) (a1 C(z x)) :\/: (a2 C(z y))+data (a1 :/\: a2) C(x y) = FORALL(z) (a1 C(x z)) :/\: (a2 C(y z))+class MyEq p where+ -- Minimal definition defines any one of unsafeCompare, =\/= and =/\=.+ unsafeCompare :: p C(a b) -> p C(c d) -> Bool+ unsafeCompare a b = IsEq == (a =/\= unsafeCoerceP b)+ (=\/=) :: p C(a b) -> p C(a c) -> EqCheck C(b c)+ a =\/= b | unsafeCompare a b = unsafeCoerceP IsEq+ | otherwise = NotEq+ (=/\=) :: p C(a c) -> p C(b c) -> EqCheck C(a b)+ a =/\= b | IsEq == (a =\/= unsafeCoerceP b) = unsafeCoerceP IsEq+ | otherwise = NotEq++infix 4 =\/=, =/\=++unsafeCoerceP :: a C(x y) -> a C(b c)+unsafeCoerceP = unsafeCoerce#++unsafeCoerceP2 :: t C(w x y z) -> t C(a b c d)+unsafeCoerceP2 = unsafeCoerce#++instance (Show2 a, Show2 b) => Show ( (a :> b) C(x y) ) where+ showsPrec d (x :> y) = showOp2 1 ":>" d x y++instance (Show2 a, Show2 b) => Show2 (a :> b) where+ showsPrec2 = showsPrec++instance (Show2 a, Show2 b) => Show ( (a :\/: b) C(x y) ) where+ showsPrec d (x :\/: y) = showOp2 9 ":\\/:" d x y++instance (Show2 a, Show2 b) => Show2 (a :\/: b) where+ showsPrec2 = showsPrec++infixr 5 :>:, :<:, +>+, +<+++-- forward list+data FL a C(x z) where+ (:>:) :: a C(x y) -> FL a C(y z) -> FL a C(x z)+ NilFL :: FL a C(x x)++instance Show2 a => Show (FL a C(x z)) where+ showsPrec _ NilFL = showString "NilFL"+ showsPrec d (x :>: xs) = showParen (d > prec) $ showsPrec2 (prec + 1) x .+ showString " :>: " . showsPrec (prec + 1) xs+ where prec = 5++instance Show2 a => Show2 (FL a) where+ showsPrec2 = showsPrec++-- reverse list+data RL a C(x z) where+ (:<:) :: a C(y z) -> RL a C(x y) -> RL a C(x z)+ NilRL :: RL a C(x x)++nullFL :: FL a C(x z) -> Bool+nullFL NilFL = True+nullFL _ = False++nullRL :: RL a C(x z) -> Bool+nullRL NilRL = True+nullRL _ = False++filterFL :: (FORALL(x y) p C(x y) -> EqCheck C(x y)) -> FL p C(w z) -> FL p C(w z)+filterFL _ NilFL = NilFL+filterFL f (x:>:xs) | IsEq <- f x = filterFL f xs+ | otherwise = x :>: filterFL f xs++filterE :: (a -> EqCheck C(x y)) -> [a] -> [Proof a C(x y)]+filterE _ [] = []+filterE p (x:xs)+ | IsEq <- p x = Proof x : filterE p xs+ | otherwise = filterE p xs++(+>+) :: FL a C(x y) -> FL a C(y z) -> FL a C(x z)+NilFL +>+ ys = ys+(x:>:xs) +>+ ys = x :>: xs +>+ ys++(+<+) :: RL a C(y z) -> RL a C(x y) -> RL a C(x z)+NilRL +<+ ys = ys+(x:<:xs) +<+ ys = x :<: xs +<+ ys++reverseFL :: FL a C(x z) -> RL a C(x z)+reverseFL xs = r NilRL xs+ where r :: RL a C(l m) -> FL a C(m o) -> RL a C(l o)+ r ls NilFL = ls+ r ls (a:>:as) = r (a:<:ls) as++reverseRL :: RL a C(x z) -> FL a C(x z)+reverseRL xs = r NilFL xs -- r (xs :> NilFL)+ where r :: FL a C(m o) -> RL a C(l m) -> FL a C(l o)+ r ls NilRL = ls+ r ls (a:<:as) = r (a:>:ls) as++concatFL :: FL (FL a) C(x z) -> FL a C(x z)+concatFL NilFL = NilFL+concatFL (a:>:as) = a +>+ concatFL as++concatRL :: RL (RL a) C(x z) -> RL a C(x z)+concatRL NilRL = NilRL+concatRL (a:<:as) = a +<+ concatRL as++spanFL :: (FORALL(w y) a C(w y) -> Bool) -> FL a C(x z) -> (FL a :> FL a) C(x z)+spanFL f (x:>:xs) | f x = case spanFL f xs of+ ys :> zs -> (x:>:ys) :> zs+spanFL _ xs = NilFL :> xs++splitAtFL :: Int -> FL a C(x z) -> (FL a :> FL a) C(x z)+splitAtFL 0 xs = NilFL :> xs+splitAtFL _ NilFL = NilFL :> NilFL+splitAtFL n (x:>:xs) = case splitAtFL (n-1) xs of+ (xs':>xs'') -> (x:>:xs' :> xs'')++-- 'bunchFL n' groups patches into batches of n, except that it always puts+-- the first patch in its own group, this being a recognition that the+-- first patch is often *very* large.++bunchFL :: Int -> FL a C(x y) -> FL (FL a) C(x y)+bunchFL _ NilFL = NilFL+bunchFL n (x:>:xs) = (x :>: NilFL) :>: bFL xs+ where bFL :: FL a C(x y) -> FL (FL a) C(x y)+ bFL NilFL = NilFL+ bFL bs = case splitAtFL n bs of+ a :> b -> a :>: bFL b+++allFL :: (FORALL(x y) a C(x y) -> Bool) -> FL a C(w z) -> Bool+allFL f xs = and $ mapFL f xs++foldlFL :: (FORALL(w y) a -> b C(w y) -> a) -> a -> FL b C(x z) -> a+foldlFL _ x NilFL = x+foldlFL f x (y:>:ys) = foldlFL f (f x y) ys++foldlRL :: (FORALL(w y) a -> b C(w y) -> a) -> a -> RL b C(x z) -> a+foldlRL _ x NilRL = x+foldlRL f x (y:<:ys) = foldlRL f (f x y) ys++mapFL_FL :: (FORALL(w y) a C(w y) -> b C(w y)) -> FL a C(x z) -> FL b C(x z)+mapFL_FL _ NilFL = NilFL+mapFL_FL f (a:>:as) = f a :>: mapFL_FL f as++zipWithFL :: (FORALL(x y) a -> p C(x y) -> q C(x y))+ -> [a] -> FL p C(w z) -> FL q C(w z)+zipWithFL f (x:xs) (y :>: ys) = f x y :>: zipWithFL f xs ys+zipWithFL _ _ NilFL = NilFL+zipWithFL _ [] (_:>:_) = bug "zipWithFL called with too short a list"++mapRL_RL :: (FORALL(w y) a C(w y) -> b C(w y)) -> RL a C(x z) -> RL b C(x z)+mapRL_RL _ NilRL = NilRL+mapRL_RL f (a:<:as) = f a :<: mapRL_RL f as++mapFL :: (FORALL(w z) a C(w z) -> b) -> FL a C(x y) -> [b]+mapFL _ NilFL = []+mapFL f (a :>: b) = f a : mapFL f b++mapRL :: (FORALL(w z) a C(w z) -> b) -> RL a C(x y) -> [b]+mapRL _ NilRL = []+mapRL f (a :<: b) = f a : mapRL f b++unsafeMap_l2f :: (FORALL(w z) a -> b C(w z)) -> [a] -> FL b C(x y)+unsafeMap_l2f _ [] = unsafeCoerceP NilFL+unsafeMap_l2f f (x:xs) = f x :>: unsafeMap_l2f f xs++lengthFL :: FL a C(x z) -> Int+lengthFL xs = l xs 0+ where l :: FL a C(x z) -> Int -> Int+ l NilFL n = n+ l (_:>:as) n = l as $! n+1++lengthRL :: RL a C(x z) -> Int+lengthRL xs = l xs 0+ where l :: RL a C(x z) -> Int -> Int+ l NilRL n = n+ l (_:<:as) n = l as $! n+1++isShorterThanRL :: RL a C(x y) -> Int -> Bool+isShorterThanRL _ n | n <= 0 = False+isShorterThanRL NilRL _ = True+isShorterThanRL (_:<:xs) n = isShorterThanRL xs (n-1)++concatReverseFL :: FL (RL a) C(x y) -> RL a C(x y)+concatReverseFL = concatRL . reverseFL++headRL :: RL a C(x y) -> FlippedSeal a C(y)+headRL (x:<:_) = flipSeal x+headRL _ = impossible++consRLSealed :: a C(y z) -> FlippedSeal (RL a) C(y) -> FlippedSeal (RL a) C(z)+consRLSealed a (FlippedSeal as) = flipSeal $ a :<: as++#ifndef GADT_WITNESSES+-- These are useful for interfacing with modules outside of+-- patch theory, such as Show.lhs+unsafeUnFL :: FL a -> [a]+unsafeUnFL NilFL = []+unsafeUnFL (a:>:as) = a : unsafeUnFL as++unsafeUnRL :: RL a -> [a]+unsafeUnRL NilRL = []+unsafeUnRL (a:<:as) = a : unsafeUnRL as++unsafeFL :: [a] -> FL a+unsafeFL [] = NilFL+unsafeFL (a:as) = a :>: unsafeFL as++unsafeRL :: [a] -> RL a+unsafeRL [] = NilRL+unsafeRL (a:as) = a :<: unsafeRL as+#endif
@@ -0,0 +1,151 @@+% Copyright (C) 2002-2003 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\chapter{Theory of patches}+\label{Patch}++\newtheorem{thm}{Theorem}+\newtheorem{dfn}{Definition}++\section{Background}++I think a little background on the author is in order. I am a physicist,+and think like a physicist. The proofs and theorems given here are what I+would call ``physicist'' proofs and theorems, which is to say that while+the proofs may not be rigorous, they are practical, and the theorems are+intended to give physical insight. It would be great to have a+mathematician work on this to give patch theory better formalized+foundations.++From the beginning of this theory, which originated as the result of a+series of email discussions with Tom Lord, I have looked at patches as+being analogous to the operators of quantum mechanics. I include in this+appendix footnotes explaining the theory of patches in terms of the theory+of quantum mechanics. I advise against taking this analogy too seriously,+although it could provide some insight into how a physicist might think+about darcs.++\begin{code}+{-# OPTIONS_GHC -cpp -fno-warn-orphans #-}+#include "gadts.h"+module Darcs.Patch ( RepoPatch, Prim, Patch, RealPatch, Named, Patchy,+ flattenFL, joinPatches,+ fromPrim, fromPrims,+ is_null_patch, nullP,+ rmfile, addfile, rmdir, adddir, move,+ hunk, tokreplace, namepatch, anonymous,+ binary,+ description,+ showContextPatch, showPatch, showNicely,+ infopatch, changepref,+ thing, things,+ is_similar, is_addfile, is_hunk, is_setpref,+#ifndef GADT_WITNESSES+ merger, is_merger, merge,+ commute, commutex, list_touched_files,+ -- for PatchTest+ unravel, elegant_merge,+#else+ Commute(..),+#endif+ resolve_conflicts,+ Effect, effect,+ is_binary, gzWritePatch, writePatch, is_adddir,+ invert, invertFL, invertRL, identity,+ commuteFL, commuteRL,+ readPatch,+ canonize, sort_coalesceFL,+ try_to_shrink,+ apply_to_slurpy, patchname, patchcontents,+ apply_to_filepaths, force_replace_slurpy, apply,+ patch2patchinfo,+ LineMark(AddedLine, RemovedLine, AddedRemovedLine, None),+ MarkedUpFile, markup_file, empty_markedup_file,+ summary, summarize, xml_summary,+ adddeps, getdeps,+ list_conflicted_files,+ modernize_patch,+ -- for Population+ DirMark(..), patchChanges, applyToPop,+ ) where+import Darcs.PopulationData ( DirMark(..) )+import Darcs.Patch.Core ( Patch, Named,+ flattenFL,+ adddeps, namepatch,+ anonymous,+#ifndef GADT_WITNESSES+ is_merger,+#endif+ getdeps,+ is_null_patch, nullP, infopatch,+ patch2patchinfo, patchname, patchcontents )+import Darcs.Patch.Read ( readPatch )+import Darcs.Patch.Patchy ( Patchy, writePatch, gzWritePatch,+ showPatch, showNicely, showContextPatch,+ invert, invertRL, invertFL, identity,+ thing, things,+ commuteFL, commuteRL, apply,+ description, summary,+#ifndef GADT_WITNESSES+ commute, commutex, list_touched_files,+#else+ Commute(..)+#endif+ )+import Darcs.Patch.Viewing ( xml_summary, summarize )+import Darcs.Patch.Apply ( applyToPop, patchChanges, empty_markedup_file,+ markup_file, force_replace_slurpy,+ apply_to_filepaths, apply_to_slurpy,+ LineMark(..), MarkedUpFile )+import Darcs.Patch.Commute ( modernize_patch,+#ifndef GADT_WITNESSES+ unravel,+ merger, merge, elegant_merge,+#endif+ )+import Darcs.Patch.Prim ( FromPrims, fromPrims, joinPatches, FromPrim, fromPrim,+ Conflict, Effect(effect), list_conflicted_files, resolve_conflicts,+ Prim, canonize,+ sort_coalesceFL,+ rmdir, rmfile, tokreplace, adddir, addfile,+ binary, changepref, hunk, move, + is_adddir, is_addfile,+ is_hunk, is_binary, is_setpref,+ is_similar,+ try_to_shrink )+import Darcs.Ordered ( FL )+import Darcs.Patch.Real ( RealPatch )++instance Patchy Patch+instance (Conflict p, Effect p, Patchy p) => Patchy (Named p)++class (Patchy p, Effect p, FromPrim p, Conflict p) => RepoPatchBase p+instance RepoPatchBase Patch+instance RepoPatchBase RealPatch++class (Patchy p, Effect p, FromPrims p, Conflict p) => RepoPatch p+instance RepoPatch Patch+instance RepoPatchBase p => RepoPatch (FL p)++\end{code}++\input{Darcs/Patch/Apply.lhs}+\input{Darcs/Patch/Core.lhs}+\input{Darcs/Patch/Prim.lhs}+\input{Darcs/Patch/Commute.lhs}+\input{Darcs/Patch/Show.lhs}+
@@ -0,0 +1,522 @@+% Copyright (C) 2002-2005 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.+++\begin{code}+{-# OPTIONS_GHC -cpp -fglasgow-exts -fno-warn-orphans #-}+{-# LANGUAGE CPP #-}++#include "gadts.h"++module Darcs.Patch.Apply ( apply_to_filepaths, apply_to_slurpy,+ forceTokReplace,+ markup_file, empty_markedup_file,+ patchChanges,+ applyToPop,+ LineMark(..), MarkedUpFile,+ force_replace_slurpy )+ where++import Prelude hiding ( catch, pi )+import Darcs.Flags ( DarcsFlag( SetScriptsExecutable ) )++import qualified Data.ByteString.Char8 as BC (split, break, pack, singleton)++import qualified Data.ByteString as B (ByteString, null, empty, concat, isPrefixOf)+import ByteStringUtils ( linesPS, unlinesPS, break_after_nth_newline, break_before_nth_newline, )++import Darcs.Patch.FileName ( fn2ps, fn2fp, fp2fn,+ movedirfilename )+import Darcs.PopulationData ( Population(..), PopTree(..), Info(..), DirMark(..) )+import Data.List ( intersperse )+import Data.Maybe ( catMaybes )+import Darcs.Patch.Patchy ( Apply, apply, applyAndTryToFixFL, applyAndTryToFix,+ mapMaybeSnd )+import Darcs.Patch.Commute ()+import Darcs.Patch.Core ( Patch(..), Named(..) )+import Darcs.Patch.Prim ( Prim(..), Effect(effect),+ DirPatchType(..), FilePatchType(..),+ try_tok_internal )+import Darcs.Patch.Info ( PatchInfo )+import Control.Monad ( when )+import Darcs.SlurpDirectory ( FileContents, Slurpy, withSlurpy, slurp_modfile )+import RegChars ( regChars )+import Darcs.Repository.Prefs ( change_prefval )+import Darcs.Global ( darcsdir )+import Darcs.IO ( WriteableDirectory(..), ReadableDirectory(..) )+import Darcs.FilePathMonad ( withFilePaths )+#include "impossible.h"+import Darcs.Ordered ( FL(..), (:>)(..),+ mapFL, mapFL_FL, spanFL, foldlFL )+\end{code}++++\section{Introduction}++A patch describes a change to the tree. It could be either a primitive+patch (such as a file add/remove, a directory rename, or a hunk replacement+within a file), or a composite patch describing many such changes. Every+patch type must satisfy the conditions described in this appendix. The+theory of patches is independent of the data which the patches manipulate,+which is what makes it both powerful and useful, as it provides a framework+upon which one can build a revision control system in a sane manner.++Although in a sense, the defining property of any patch is that it can be+applied to a certain tree, and thus make a certain change, this change does+not wholly define the patch. A patch is defined by a+\emph{representation}, together with a set of rules for how it behaves+(which it has in common with its patch type). The \emph{representation} of+a patch defines what change that particular patch makes, and must be+defined in the context of a specific tree. The theory of patches is a+theory of the many ways one can change the representation of a patch to+place it in the context of a different tree. The patch itself is not+changed, since it describes a single change, which must be the same+regardless of its representation\footnote{For those comfortable with+quantum mechanics, think of a patch as a quantum mechanical operator, and+the representation as the basis set. The analogy breaks down pretty+quickly, however, since an operator could be described in any complete+basis set, while a patch modifying the file {\tt foo} can only be described+in the rather small set of contexts which have a file {\tt foo} to be+modified.}.++So how does one define a tree, or the context of a patch? The simplest way+to define a tree is as the result of a series of patches applied to the+empty tree\footnote{This is very similar to the second-quantized picture,+in which any state is seen as the result of a number of creation operators+acting on the vacuum, and provides a similar set of simplifications---in+particular, the exclusion principle is very elegantly enforced by the+properties of the anti-hermitian fermion creation operators.}. Thus, the+context of a patch consists of the set of patches that precede it.++\section{Applying patches}+++\begin{code}+apply_to_filepaths :: Apply p => p C(x y) -> [FilePath] -> [FilePath]+apply_to_filepaths pa fs = withFilePaths fs (apply [] pa)++apply_to_slurpy :: (Apply p, Monad m) => p C(x y) -> Slurpy -> m Slurpy+apply_to_slurpy p s = case withSlurpy s (apply [] p) of+ Left err -> fail err+ Right (s', ()) -> return s'++instance Apply p => Apply (Named p) where+ apply opts (NamedP _ _ p) = apply opts p+ applyAndTryToFix (NamedP n d p) = mapMaybeSnd (NamedP n d) `fmap` applyAndTryToFix p++instance Apply Patch where+ apply opts p = applyFL opts $ effect p+ applyAndTryToFixFL (PP x) = mapMaybeSnd (mapFL_FL PP) `fmap` applyAndTryToFixFL x+ applyAndTryToFixFL (ComP xs) = mapMaybeSnd (\xs' -> ComP xs' :>: NilFL) `fmap` applyAndTryToFix xs+ applyAndTryToFixFL x = do apply [] x; return Nothing+ applyAndTryToFix (ComP xs) = mapMaybeSnd ComP `fmap` applyAndTryToFix xs+ applyAndTryToFix x = do mapMaybeSnd ComP `fmap` applyAndTryToFixFL x++force_replace_slurpy :: Prim C(x y) -> Slurpy -> Maybe Slurpy+force_replace_slurpy (FP f (TokReplace tcs old new)) s =+ slurp_modfile f (forceTokReplace tcs old new) s+force_replace_slurpy _ _ = bug "Can only force_replace_slurpy on a replace."++instance Apply Prim where+ apply opts (Split ps) = applyFL opts ps+ apply _ Identity = return ()+ apply _ (FP f RmFile) = mRemoveFile f+ apply _ (FP f AddFile) = mCreateFile f+ apply opts p@(FP _ (Hunk _ _ _)) = applyFL opts (p :>: NilFL)+ apply _ (FP f (TokReplace t o n)) = mModifyFilePSs f doreplace+ where doreplace ls =+ case mapM (try_tok_internal t (BC.pack o) (BC.pack n)) ls of+ Nothing -> fail $ "replace patch to " ++ fn2fp f+ ++ " couldn't apply."+ Just ls' -> return $ map B.concat ls'+ apply _ (FP f (Binary o n)) = mModifyFilePS f doapply+ where doapply oldf = if o == oldf+ then return n+ else fail $ "binary patch to " ++ fn2fp f+ ++ " couldn't apply."+ apply _ (DP d AddDir) = mCreateDirectory d+ apply _ (DP d RmDir) = mRemoveDirectory d+ apply _ (Move f f') = mRename f f'+ apply _ (ChangePref p f t) =+ do b <- mDoesDirectoryExist (fp2fn $ darcsdir++"/prefs")+ when b $ change_prefval p f t+ applyAndTryToFixFL (FP f RmFile) =+ do x <- mReadFilePS f+ if B.null x then do mRemoveFile f+ return Nothing+ else do mWriteFilePS f B.empty+ mRemoveFile f+ return $ Just ("WARNING: Fixing removal of non-empty file "++fn2fp f,+ FP f (Binary x B.empty) :>: FP f RmFile :>: NilFL )+ applyAndTryToFixFL p = do apply [] p; return Nothing++applyFL :: WriteableDirectory m => [DarcsFlag] -> FL Prim C(x y) -> m ()+applyFL _ NilFL = return ()+applyFL opts ((FP f h@(Hunk _ _ _)):>:the_ps)+ = case spanFL f_hunk the_ps of+ (xs :> ps') ->+ do let foo = h :>: mapFL_FL (\(FP _ h') -> h') xs+ mModifyFilePS f $ hunkmod foo+ case h of+ (Hunk 1 _ (n:_)) | BC.pack "#!" `B.isPrefixOf` n &&+ SetScriptsExecutable `elem` opts+ -> mSetFileExecutable f True+ _ -> return ()+ applyFL opts ps'+ where f_hunk (FP f' (Hunk _ _ _)) | f == f' = True+ f_hunk _ = False+ hunkmod :: WriteableDirectory m => FL FilePatchType C(x y)+ -> B.ByteString -> m B.ByteString+ hunkmod NilFL ps = return ps+ hunkmod (Hunk line old new:>:hs) ps+ = case applyHunkLines [(line,old,new)] ps of+ Just ps' -> hunkmod hs ps'+ Nothing -> fail $ "Error applying hunk to file " ++ fn2fp f+ hunkmod _ _ = impossible+applyFL opts (p:>:ps) = do apply opts p+ applyFL opts ps+\end{code}++\subsection{Hunk patches}++Hunks are an example of a complex filepatch. A hunk is a set of lines of a+text file to be replaced by a different set of lines. Either of these sets+may be empty, which would mean a deletion or insertion of lines.+\begin{code}+applyHunks :: [(Int, [B.ByteString], [B.ByteString])]+ -> B.ByteString -> Maybe [B.ByteString]+applyHunks [] ps = Just [ps]+applyHunks ((l, [], n):hs) ps+ = case break_before_nth_newline (l - 2) ps of+ (prfix, after_prefix) -> do rest <- applyHunks hs after_prefix+ return $ intersperse nl (prfix:n) ++ rest+ where nl = BC.singleton '\n'+applyHunks ((l, o, n):hs) ps+ = case break_before_nth_newline (l - 2) ps of+ (prfix, after_prefix) ->+ case break_before_nth_newline (length o) after_prefix of+ (oo, _) | oo /= unlinesPS (B.empty:o) -> fail "applyHunks error"+ (_, suffix) ->+ do rest <- applyHunks hs suffix+ return $ intersperse nl (prfix:n) ++ rest+ where nl = BC.singleton '\n'++applyHunkLines :: [(Int, [B.ByteString], [B.ByteString])]+ -> FileContents -> Maybe FileContents+applyHunkLines [] c = Just c+applyHunkLines [(1, [], n)] ps | B.null ps = Just $ unlinesPS (n++[B.empty])+applyHunkLines hs@((l, o, n):hs') ps =+ do pss <- case l of+ 1 -> case break_after_nth_newline (length o) ps of+ Nothing -> if ps == unlinesPS o+ then return $ intersperse nl n+ else fail "applyHunkLines: Unexpected hunks"+ Just (shouldbeo, suffix)+ | shouldbeo /= unlinesPS (o++[B.empty]) ->+ fail $ "applyHunkLines: Bad patch!"+ | null n ->+ do x <- applyHunkLines hs' suffix+ return [x]+ | otherwise ->+ do rest <- applyHunks hs' suffix+ return $ intersperse nl n ++ nl:rest+ _ | l < 0 -> bug "Prim.applyHunkLines: After -ve lines?"+ | otherwise -> applyHunks hs ps+ let result = B.concat pss+ return result+ where nl = BC.singleton '\n'+\end{code}++\subsection{Token replace patches}\label{token_replace}++Although most filepatches will be hunks, darcs is clever enough to support+other types of changes as well. A ``token replace'' patch replaces all+instances of a given token with some other version. A token, here, is+defined by a regular expression, which must be of the simple [a--z\ldots]\ type,+indicating which characters are allowed in a token, with all other+characters acting as delimiters. For example, a C identifier would be a+token with the flag \verb![A-Za-z_0-9]!.++\begin{code}+forceTokReplace :: String -> String -> String+ -> FileContents -> Maybe FileContents+forceTokReplace t os ns c = Just $ unlinesPS $ map forceReplace $ linesPS c+ where o = BC.pack os+ n = BC.pack ns+ tokchar = regChars t+ toks_and_intratoks ps | B.null ps = []+ toks_and_intratoks ps =+ let (before,s') = BC.break tokchar ps+ (tok, after) = BC.break (not . tokchar) s'+ in before : tok : toks_and_intratoks after+ forceReplace ps = B.concat $ map o_t_n $ toks_and_intratoks ps+ o_t_n s | s == o = n+ | otherwise = s+\end{code}++What makes the token replace patch special is the fact that a token replace+can be merged with almost any ordinary hunk, giving exactly what you would+want. For example, you might want to change the patch type {\tt+TokReplace} to {\tt TokenReplace} (if you decided that saving two+characters of space was stupid). If you did this using hunks, it would+modify every line where {\tt TokReplace} occurred, and quite likely provoke+a conflict with another patch modifying those lines. On the other hand, if+you did this using a token replace patch, the only change that it could+conflict with would be if someone else had used the token ``{\tt+TokenReplace}'' in their patch rather than TokReplace---and that actually+would be a real conflict!++%\section{Outputting interesting and useful information}++%Just being able to manipulate patches and trees is not enough. We also+%want to be able to view the patches and files. This requires another set+%of functions, closely related to the patch application functions, which+%will give us the necessary information to browse the changes we have made.+%It is \emph{not} the Patch module's responsibility to add any sort of+%markup or formatting, but simply to provide the information necessary for an+%external module to do the formatting.++\begin{code}+data LineMark = AddedLine PatchInfo | RemovedLine PatchInfo+ | AddedRemovedLine PatchInfo PatchInfo | None+ deriving (Show)+type MarkedUpFile = [(B.ByteString, LineMark)]+empty_markedup_file :: MarkedUpFile+empty_markedup_file = [(B.empty, None)]++markup_file :: Effect p => PatchInfo -> p C(x y)+ -> (FilePath, MarkedUpFile) -> (FilePath, MarkedUpFile)+markup_file x p = mps (effect p)+ where mps :: FL Prim C(a b) -> (FilePath, MarkedUpFile) -> (FilePath, MarkedUpFile)+ mps NilFL = id+ mps (pp:>:pps) = mps pps . markup_prim x pp++markup_prim :: PatchInfo -> Prim C(x y)+ -> (FilePath, MarkedUpFile) -> (FilePath, MarkedUpFile)+markup_prim _ (Split NilFL) (f, mk) = (f, mk)+markup_prim n (Split (p:>:ps)) (f, mk) = markup_prim n (Split ps) $+ markup_prim n p (f, mk)+markup_prim _ (FP _ AddFile) (f, mk) = (f, mk)+markup_prim _ (FP _ RmFile) (f, mk) = (f, mk)+markup_prim n (FP f' (Hunk line old new)) (f, mk)+ | fn2fp f' /= f = (f, mk)+ | otherwise = (f, markup_hunk n line old new mk)+markup_prim name (FP f' (TokReplace t o n)) (f, mk)+ | fn2fp f' /= f = (f, mk)+ | otherwise = (f, markup_tok name t o n mk)+markup_prim _ (DP _ _) (f, mk) = (f, mk)+markup_prim _ (Move d d') (f, mk) = (fn2fp $ movedirfilename d d' (fp2fn f), mk)+markup_prim _ (ChangePref _ _ _) (f,mk) = (f,mk)+markup_prim _ Identity (f,mk) = (f,mk)+markup_prim n (FP f' (Binary _ _)) (f,mk)+ | fn2fp f' == f = (f,(BC.pack "Binary file", AddedLine n):mk)+ | otherwise = (f,mk)++markup_hunk :: PatchInfo -> Int -> [B.ByteString] -> [B.ByteString]+ -> MarkedUpFile -> MarkedUpFile+markup_hunk n l old new ((sf, RemovedLine pi):mk) =+ (sf, RemovedLine pi) : markup_hunk n l old new mk+markup_hunk n l old new ((sf, AddedRemovedLine po pn):mk) =+ (sf, AddedRemovedLine po pn) : markup_hunk n l old new mk++markup_hunk name 1 old (n:ns) mk =+ (n, AddedLine name) : markup_hunk name 1 old ns mk+markup_hunk n 1 (o:os) [] ((sf, None):mk)+ | o == sf = (sf, RemovedLine n) : markup_hunk n 1 os [] mk+ | otherwise = [(BC.pack "Error in patch application", AddedLine n)]+markup_hunk n 1 (o:os) [] ((sf, AddedLine nold):mk)+ | o == sf = (sf, AddedRemovedLine nold n) : markup_hunk n 1 os [] mk+ | otherwise = [(BC.pack "Error in patch application", AddedLine n)]+markup_hunk _ 1 [] [] mk = mk++markup_hunk n l old new ((sf, AddedLine pi):mk)+ | l > 1 = (sf, AddedLine pi) : markup_hunk n (l-1) old new mk+ | l < 1 = (sf, AddedLine pi) : markup_hunk n (l-1) old new mk+markup_hunk n l old new ((sf, None):mk)+ | l > 1 = (sf, None) : markup_hunk n (l-1) old new mk+ | l < 1 = (sf, None) : markup_hunk n (l-1) old new mk++markup_hunk _ _ _ _ [] = []++markup_hunk _ _ _ _ mk = (BC.pack "Error: ",None) : mk++markup_tok :: PatchInfo -> String -> String -> String+ -> MarkedUpFile -> MarkedUpFile+markup_tok name t ostr nstr mk = concatMap mt mk+ where o = BC.pack ostr+ n = BC.pack nstr+ mt (sf, AddedLine pi) =+ case B.concat `fmap` try_tok_internal t o n sf of+ Just sf' | sf' == sf -> [(sf, AddedLine pi)]+ | otherwise -> [(sf, AddedRemovedLine pi name),+ (sf', AddedLine name)]+ Nothing ->+ [(sf, AddedLine pi),+ (BC.pack "There seems to be an inconsistency...", None),+ (BC.pack "Please run darcs check.", None)]+ mt mark = [mark]+\end{code}++%files or directories, changed by a patch+%we get it solely from the patch here+%instead of performing patch apply on a population+%we !could! achieve the same by applying a patch to a cleaned population+%and getting modified files and dirs+%but this should be significantly slower when the population grows large+%This could be useful for just presenting a summary of what a patch does+%(especially useful for larger repos)++\begin{code}+patchChanges :: Prim C(x y) -> [(String,DirMark)]+patchChanges (Move f1 f2) = [(fn2fp f1,MovedFile $ fn2fp f2),+ (fn2fp f2,MovedFile $ fn2fp f1)]+patchChanges (DP d AddDir) = [(fn2fp d,AddedDir)]+patchChanges (DP d RmDir) = [(fn2fp d,RemovedDir)]+patchChanges (FP f AddFile) = [(fn2fp f,AddedFile)]+patchChanges (FP f RmFile) = [(fn2fp f,RemovedFile)]+patchChanges (FP f _) = [(fn2fp f,ModifiedFile)]+patchChanges (Split ps) = concat $ mapFL patchChanges ps+patchChanges (ChangePref _ _ _) = []+patchChanges Identity = []+\end{code}++%apply a patch to a population at a given time++\begin{code}+applyToPop :: PatchInfo -> FL Prim C(x y) -> Population -> Population+applyToPop _ NilFL = id+applyToPop pinf (p:>:ps) = applyToPop pinf ps . applyToPop' pinf p ++applyToPop'+ :: PatchInfo -> Prim C(x y) -> Population -> Population+applyToPop' pi patch (Pop _ tree)+ = Pop pi (applyToPopTree patch tree)+ -- ``pi'' is global below!+ where applyToPopTree :: Prim C(x y) -> PopTree -> PopTree+ applyToPopTree (Split ps) tr =+ foldlFL (\t p -> applyToPopTree p t) tr ps+ applyToPopTree p@(FP f AddFile) tr =+ let xxx = BC.split '/' (fn2ps f) in+ popChange xxx p $ fst $ breakP xxx tr+ applyToPopTree p@(FP f _) tr = popChange (BC.split '/' (fn2ps f)) p tr+ applyToPopTree p@(DP f AddDir) tr =+ let xxx = BC.split '/' (fn2ps f) in+ popChange xxx p $ fst $ breakP xxx tr+ applyToPopTree p@(DP d _) tr = popChange (BC.split '/' (fn2ps d)) p tr+ -- precondition: ``to'' does not exist yet!+ applyToPopTree (Move from to) tr+ = case breakP (BC.split '/' (fn2ps from)) $+ fst $ breakP (BC.split '/' $ fn2ps to) tr of+ (tr',Just ins) ->+ let to' = (BC.split '/' (fn2ps to))+ ins' = case ins of+ PopDir i trs -> PopDir (i {nameI = last to',+ modifiedByI = pi,+ modifiedHowI = MovedDir (fn2fp from)})+ trs+ PopFile i -> PopFile (i {nameI = last to',+ modifiedByI = pi,+ modifiedHowI = MovedFile (fn2fp from)})+ in insertP to' tr' ins'+ _ -> tr -- ignore the move if ``from'' couldn't be found+ applyToPopTree (ChangePref _ _ _) tr = tr+ applyToPopTree Identity tr = tr++ -- insert snd arg into fst arg+ insertP :: [B.ByteString] -> PopTree -> PopTree -> PopTree+ insertP [parent,_] org@(PopDir f trs) tr+ | parent == (nameI f) = PopDir f (tr:trs)+ | otherwise = org+ insertP (n:rest) org@(PopDir f trs) tr+ | (nameI f) == n = PopDir f trs'+ | otherwise = org+ where trs' = map (\o -> insertP rest o tr) trs+ insertP _ org _ = org++ -- change a population according to a patch+ popChange :: [B.ByteString] -> Prim C(x y) -> PopTree -> PopTree+ popChange [parent,path] (DP d AddDir) tr@(PopDir f trs)+ | parent == (nameI f) = PopDir f (new:trs)+ | otherwise = tr+ where new = PopDir (Info {nameI = path,+ modifiedByI = pi,+ modifiedHowI = AddedDir,+ createdByI = Just pi,+ creationNameI = Just $ fn2ps d}) []+ -- only mark a directory (and contents) as ``deleted'' do not delete it actually+ popChange [path] (DP _ RmDir) tr@(PopDir f trs)+ | path == (nameI f) = PopDir (f {modifiedByI = pi,+ modifiedHowI = RemovedDir}) trs'+ | otherwise = tr+ where trs' = map markDel trs -- recursively ``delete'' the contents++ popChange [parent,path] (FP d AddFile) tr@(PopDir f trs)+ | parent == (nameI f) = PopDir f (new:trs)+ | otherwise = tr+ where new = PopFile (Info {nameI = path,+ modifiedByI = pi,+ modifiedHowI = AddedFile,+ createdByI = Just pi,+ creationNameI = Just $ fn2ps d})+ popChange [path] (FP _ RmFile) tr@(PopFile f)+ | path == (nameI f) = PopFile (f {modifiedByI = pi,+ modifiedHowI = RemovedFile})+ | otherwise = tr+ popChange [path] (FP _ _) (PopFile f)+ | path == (nameI f)+ = PopFile (f {modifiedByI = pi,+ modifiedHowI = if modifiedHowI f == AddedFile && modifiedByI f == pi+ then AddedFile+ else ModifiedFile})+ popChange (n:rest) p tr@(PopDir f trs)+ | (nameI f) == n = PopDir f (map (popChange rest p) trs)+ | otherwise = tr+ popChange _ _ tr = tr+ markDel (PopDir f trs) = PopDir (f {modifiedByI = pi,+ modifiedHowI = RemovedDir}) trs'+ where trs' = map markDel trs+ markDel (PopFile f) = PopFile (f {modifiedByI = pi,+ modifiedHowI = RemovedFile})++-- break a poptree fst: org tree with subtree removed,+-- snd: removed subtree+breakP :: [B.ByteString] -> PopTree -> (PopTree,Maybe PopTree)+breakP [parent,path] tr@(PopDir f trees)+ | parent == (nameI f) = case findRem path trees of+ Just (trees',tree') -> (PopDir f trees',Just tree')+ _ -> (tr,Nothing)+ | otherwise = (tr,Nothing)+ where findRem _ [] = Nothing+ findRem the_path (d:trs)+ | the_path == pname d = Just (trs,d)+ | otherwise = do (trs',d') <- findRem the_path trs+ return (d:trs',d')+breakP (n:rest) tr@(PopDir f trs)+ | (nameI f) == n = case catMaybes inss of+ [ins] -> (PopDir f trs', Just ins)+ [] -> (tr,Nothing)+ _ -> error "breakP: more than one break"+ | otherwise = (tr,Nothing)+ where (trs',inss) = unzip (map (breakP rest) trs)+breakP _ tr = (tr,Nothing)++pname :: PopTree -> B.ByteString+pname (PopDir i _) = nameI i+pname (PopFile i) = nameI i+\end{code}+
@@ -0,0 +1,211 @@+-- Copyright (C) 2002-2004,2007 David Roundy+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.++{-# OPTIONS_GHC -cpp #-}+{-# LANGUAGE CPP #-}++#include "gadts.h"++module Darcs.Patch.Bundle ( hash_bundle, make_bundle, make_bundle2, scan_bundle,+ make_context, scan_context,+ ) where++import Darcs.Flags ( DarcsFlag( Unified ) )+import Darcs.Hopefully ( PatchInfoAnd, piap,+ patchInfoAndPatch,+ unavailable, hopefully )+import Darcs.Patch ( RepoPatch, Named, showPatch, showContextPatch, readPatch )+import Darcs.Patch.Info ( PatchInfo, readPatchInfo, showPatchInfo, human_friendly, is_tag )+import Darcs.Patch.Set ( PatchSet, SealedPatchSet )+import Darcs.Ordered ( RL(..), FL(..), unsafeCoerceP,+ reverseFL, (+<+), mapFL, mapFL_FL )+import Printer ( Doc, renderPS, newline, text, ($$),+ (<>), vcat, vsep, renderString )+import Darcs.SlurpDirectory ( Slurpy )++import ByteStringUtils ( linesPS, unlinesPS, dropSpace, substrPS)+import qualified Data.ByteString as B (ByteString, length, null, drop, isPrefixOf)+import qualified Data.ByteString.Char8 as BC (unpack, break, pack)++import SHA1( sha1PS )+import Darcs.Sealed ( Sealed(Sealed), mapSeal )++hash_bundle :: RepoPatch p => [PatchInfo] -> FL (Named p) C(x y) -> String+hash_bundle _ to_be_sent = sha1PS $ renderPS+ $ vcat (mapFL showPatch to_be_sent) <> newline++make_bundle :: RepoPatch p => [DarcsFlag] -> Slurpy -> [PatchInfo] -> FL (Named p) C(x y) -> Doc+make_bundle opts the_s common to_be_sent = make_bundle2 opts the_s common to_be_sent to_be_sent++-- | In make_bundle2, it is presumed that the two patch sequences are+-- identical, but that they may be lazily generated. If two different+-- patch sequences are passed, a bundle with a mismatched hash will be+-- generated, which is not the end of the world, but isn't very useful+-- either.+make_bundle2 :: RepoPatch p => [DarcsFlag] -> Slurpy -> [PatchInfo]+ -> FL (Named p) C(x y) -> FL (Named p) C(x y) -> Doc+make_bundle2 opts the_s common to_be_sent to_be_sent2 =+ text ""+ $$ text "New patches:"+ $$ text ""+ $$ the_new+ $$ text ""+ $$ text "Context:"+ $$ text ""+ $$ (vcat $ map showPatchInfo common)+ $$ text "Patch bundle hash:"+ $$ text (hash_bundle common to_be_sent2)+ $$ text ""+ where the_new = if Unified `elem` opts+ then showContextPatch the_s to_be_sent+ else vsep $ mapFL showPatch to_be_sent++scan_bundle :: RepoPatch p => B.ByteString -> Either String (SealedPatchSet p)+scan_bundle ps+ | B.null ps = Left "Bad patch bundle!"+ | otherwise =+ case silly_lex ps of+ ("New patches:",rest) ->+ case get_patches rest of+ (Sealed patches, rest') ->+ case silly_lex rest' of+ ("Context:", rest'') ->+ case get_context rest'' of+ (cont,maybe_hash) ->+ case substrPS (BC.pack "Patch bundle hash:")+ maybe_hash of+ Just n ->+ if hash_bundle cont (mapFL_FL hopefully patches)+ == fst (silly_lex $ snd $ silly_lex $+ B.drop n maybe_hash)+ then seal_up_patches patches cont+ else Left $+ "Patch bundle failed hash!\n" +++ "This probably means that the patch has been "+++ "corrupted by a mailer.\n"+++ "The most likely culprit is CRLF newlines."+ Nothing -> seal_up_patches patches cont+ (a,r) -> Left $ "Malformed patch bundle: '"++a++"' is not 'Context:'"+ ++ "\n" ++ BC.unpack r+ ("Context:",rest) ->+ case get_context rest of+ (cont, rest') ->+ case silly_lex rest' of+ ("New patches:", rest'') ->+ case parse_patches rest'' of+ Sealed ps'' -> seal_up_patches ps'' cont+ (a,_) -> Left $ "Malformed patch bundle: '" ++ a +++ "' is not 'New patches:'"+ ("-----BEGIN PGP SIGNED MESSAGE-----",rest) ->+ scan_bundle $ filter_gpg_dashes rest+ (_,rest) -> scan_bundle rest+ where seal_up_patches :: RepoPatch p => FL (PatchInfoAnd p) C(x y) -> [PatchInfo]+ -> Either String (SealedPatchSet p)+ seal_up_patches xxx yyy =+ case reverse yyy of+ (x:_) | is_tag x ->+ Right $ Sealed ((reverseFL xxx +<+ unavailable_patches yyy)+ :<: NilRL)+ -- The above NilRL isn't quite+ -- right, because ther *are*+ -- earlier patches, but we+ -- can't set this to undefined+ -- because there are+ -- situations where we look at+ -- the rest. :{++ -- bug "No more patches in patch bundle!")+ _ -> Right $ Sealed ((reverseFL xxx +<+ unavailable_patches yyy)+ :<: NilRL)++-- filter_gpg_dashes is needed because clearsigned patches escape dashes:+filter_gpg_dashes :: B.ByteString -> B.ByteString+filter_gpg_dashes ps =+ unlinesPS $ map drop_dashes $+ takeWhile (/= BC.pack "-----END PGP SIGNED MESSAGE-----") $+ dropWhile not_context_or_newpatches $ linesPS ps+ where drop_dashes x = if B.length x < 2 then x+ else if BC.pack "- " `B.isPrefixOf` x+ then B.drop 2 x+ else x+ not_context_or_newpatches s = (s /= BC.pack "Context:") &&+ (s /= BC.pack "New patches:")++unavailable_patches :: RepoPatch p => [PatchInfo] -> RL (PatchInfoAnd p) C(x y)+unavailable_patches [] = unsafeCoerceP NilRL+unavailable_patches (x:xs) = pi_unavailable x :<: unavailable_patches xs++pi_unavailable :: RepoPatch p => PatchInfo -> PatchInfoAnd p C(x y)+pi_unavailable i = (i `patchInfoAndPatch`+ unavailable ("Patch not stored in patch bundle:\n" +++ renderString (human_friendly i)))+get_context :: B.ByteString -> ([PatchInfo],B.ByteString)+get_context ps =+ case readPatchInfo ps of+ Just (pinfo,r') ->+ case get_context r' of+ (pis,r'') -> (pinfo:pis, r'')+ Nothing -> ([],ps)+(-:-) :: a C(x y) -> (Sealed (FL a C(y)),b) -> (Sealed (FL a C(x)),b)+p -:- (Sealed ps, r) = (Sealed (p:>:ps), r)+get_patches :: RepoPatch p => B.ByteString -> (Sealed (FL (PatchInfoAnd p) C(x)), B.ByteString)+get_patches ps =+ case readPatchInfo ps of+ Nothing -> (Sealed NilFL, ps)+ Just (pinfo,_) ->+ case readPatch ps of+ Nothing -> (Sealed NilFL, ps)+ Just (Sealed p, r) -> (pinfo `piap` p) -:- get_patches r+parse_patches :: RepoPatch p => B.ByteString -> Sealed (FL (PatchInfoAnd p) C(x))+parse_patches ps =+ case readPatchInfo ps of+ Nothing -> Sealed NilFL+ Just (pinfo,_) ->+ case readPatch ps of+ Nothing -> Sealed NilFL+ Just (Sealed p, r) -> ((pinfo `piap` p) :>:) `mapSeal` parse_patches r++silly_lex :: B.ByteString -> (String, B.ByteString)+silly_lex ps = (BC.unpack a, b)+ where+ (a, b) = BC.break (== '\n') (dropSpace ps)++{-+silly_lex ps = (BC.unpack $ BC.takeWhile (/='\n') ps', BC.dropWhile (/='\n') ps')+ where+ ps' = dropSpace ps+-}++make_context :: [PatchInfo] -> Doc+make_context common =+ text ""+ $$ text "Context:"+ $$ text ""+ $$ (vcat $ map showPatchInfo $ common)+ $$ text ""++scan_context :: RepoPatch p => B.ByteString -> PatchSet p C(x)+scan_context ps+ | B.null ps = error "Bad context!"+ | otherwise =+ case silly_lex ps of+ ("Context:",rest) ->+ case get_context rest of+ (cont, _) -> unavailable_patches cont :<: NilRL+ ("-----BEGIN PGP SIGNED MESSAGE-----",rest) ->+ scan_context $ filter_gpg_dashes rest+ (_,rest) -> scan_context rest
@@ -0,0 +1,335 @@+-- Copyright (C) 2002-2003 David Roundy+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.++module Darcs.Patch.Check ( PatchCheck(), do_check, file_exists, dir_exists,+ remove_file, remove_dir, create_file, create_dir,+ insert_line, delete_line, is_valid, do_verbose_check,+ file_empty,+ check_move, modify_file, FileContents(..)+ ) where++import System.IO.Unsafe ( unsafePerformIO )+import qualified Data.ByteString as B (ByteString)+import Data.List ( isPrefixOf, inits )+import Control.Monad.State ( State, evalState, runState )+import Control.Monad.State.Class ( get, put, modify )+-- use Map, not IntMap, because Map has mapKeys and IntMap hasn't+import Data.Map ( Map )+import qualified Data.Map as M ( mapKeys, delete, insert, empty, lookup, null )+import System.FilePath ( joinPath, splitDirectories )++-- | File contents are represented by a map from line numbers to line contents.+-- If for a certain line number, the line contents are Nothing, that means+-- that we are sure that that line exists, but we don't know its contents.+-- We must also store the greatest line number that is known to exist in a+-- file, to be able to exclude the possibility of it being empty without+-- knowing its contents.+data FileContents = FC { fc_lines :: Map Int B.ByteString+ , fc_maxline :: Int+ } deriving (Eq, Show)+data Prop = FileEx String | DirEx String | NotEx String+ | FileLines String FileContents+ deriving (Eq)+-- | A @KnownState@ is a simulated repository state. The repository is either+-- inconsistent, or it has two lists of properties: one list with properties+-- that hold for this repo, and one with properties that do not hold for this+-- repo. These two lists may not have any common elements: if they had, the+-- repository would be inconsistent.+data KnownState = P [Prop] [Prop]+ | Inconsistent+ deriving (Show)+instance Show Prop where+ show (FileEx f) = "FileEx "++f+ show (DirEx d) = "DirEx "++d+ show (NotEx f) = "NotEx"++f+ show (FileLines f l) = "FileLines "++f++": "++show l++-- | PatchCheck is a state monad with a simulated repository state+type PatchCheck = State KnownState++-- | The @FileContents@ structure for an empty file+empty_filecontents :: FileContents+empty_filecontents = FC M.empty 0++-- | Returns a given value if the repository state is inconsistent, and performs+-- a given action otherwise.+handle_inconsistent :: a -- ^ The value to return if the state is inconsistent+ -> PatchCheck a -- ^ The action to perform otherwise+ -> PatchCheck a+handle_inconsistent v a = do state <- get+ case state of+ Inconsistent -> return v+ _ -> a++do_check :: PatchCheck a -> a+do_check p = evalState p (P [] [])++-- | Run a check, and print the final repository state+do_verbose_check :: PatchCheck a -> a+do_verbose_check p =+ case runState p (P [] []) of+ (b, pc) -> unsafePerformIO $ do putStrLn $ show pc+ return b++-- | Returns true if the current repository state is not inconsistent+is_valid :: PatchCheck Bool+is_valid = handle_inconsistent False (return True)++has :: Prop -> [Prop] -> Bool+has _ [] = False+has k (k':ks) = k == k' || has k ks++modify_file :: String+ -> (Maybe FileContents -> Maybe FileContents)+ -> PatchCheck Bool+modify_file f change = do+ file_exists f+ c <- file_contents f+ case change c of+ Nothing -> assert_not $ FileEx f -- shorthand for "FAIL"+ Just c' -> do set_contents f c'+ is_valid++insert_line :: String -> Int -> B.ByteString -> PatchCheck Bool+insert_line f n l = do+ c <- file_contents f+ case c of+ Nothing -> assert_not $ FileEx f -- in this case, the repo is inconsistent+ Just c' -> do+ let lines' = M.mapKeys (\k -> if k >= n then k+1 else k) (fc_lines c')+ lines'' = M.insert n l lines'+ maxline' = max n (fc_maxline c')+ set_contents f (FC lines'' maxline')+ return True++-- deletes a line from a hunk patch (third argument) in the given file (first+-- argument) at the given line number (second argument)+delete_line :: String -> Int -> B.ByteString -> PatchCheck Bool+delete_line f n l = do+ c <- file_contents f+ case c of+ Nothing -> assert_not $ FileEx f+ Just c' ->+ let flines = fc_lines c'+ flines' = M.mapKeys (\k -> if k > n then k-1 else k)+ (M.delete n flines)+ maxlinenum' | n <= fc_maxline c' = fc_maxline c' - 1+ | otherwise = n - 1+ c'' = FC flines' maxlinenum'+ do_delete = do+ set_contents f c''+ is_valid+ in case M.lookup n flines of+ Nothing -> do_delete+ Just l' -> if l == l'+ then do_delete+ else assert_not $ FileEx f++set_contents :: String -> FileContents -> PatchCheck ()+set_contents f c = handle_inconsistent () $ do+ P ks nots <- get+ let ks' = FileLines f c : filter (not . is_file_lines_for f) ks+ put (P ks' nots)+ where is_file_lines_for file prop = case prop of + FileLines f' _ -> file == f'+ _ -> False++-- | Get (as much as we know about) the contents of a file in the current state.+-- Returns Nothing if the state is inconsistent.+file_contents :: String -> PatchCheck (Maybe FileContents)+file_contents f = handle_inconsistent Nothing $ do+ P ks _ <- get+ return (fic ks)+ where fic (FileLines f' c:_) | f == f' = Just c+ fic (_:ks) = fic ks+ fic [] = Just empty_filecontents++-- | Checks if a file is empty+file_empty :: String -- ^ Name of the file to check+ -> PatchCheck Bool+file_empty f = do+ c <- file_contents f+ let empty = case c of+ Just c' -> fc_maxline c' == 0 && M.null (fc_lines c')+ Nothing -> True+ if empty+ then do set_contents f empty_filecontents+ is_valid+ -- Crude way to make it inconsistent and return false:+ else assert_not $ FileEx f+ return empty++movedirfilename :: String -> String -> String -> String+movedirfilename d d' f =+ if (d ++ "/") `isPrefixOf` f+ then d'++drop (length d) f+ else if f == d+ then d'+ else f++-- | Replaces a filename by another in all paths. Returns True if the repository+-- is consistent, False if it is not.+do_swap :: String -> String -> PatchCheck Bool+do_swap f f' = handle_inconsistent False $ do+ modify (\(P ks nots) -> P (map sw ks) (map sw nots))+ return True+ where sw (FileEx a) | f `is_soe` a = FileEx $ movedirfilename f f' a+ | f' `is_soe` a = FileEx $ movedirfilename f' f a+ sw (DirEx a) | f `is_soe` a = DirEx $ movedirfilename f f' a+ | f' `is_soe` a = DirEx $ movedirfilename f' f a+ sw (FileLines a c) | f `is_soe` a = FileLines (movedirfilename f f' a) c+ | f' `is_soe` a = FileLines (movedirfilename f' f a) c+ sw (NotEx a) | f `is_soe` a = NotEx $ movedirfilename f f' a+ | f' `is_soe` a = NotEx $ movedirfilename f' f a+ sw p = p+ is_soe d1 d2 = -- is_superdir_or_equal+ d1 == d2 || (d1 ++ "/") `isPrefixOf` d2++-- | Assert a property about the repository. If the property is already present+-- in the repo state, nothing changes, and the function returns True. If it is+-- not present yet, it is added to the repo state, and the function is True. If+-- the property is already in the list of properties that do not hold for the+-- repo, the state becomes inconsistent, and the function returns false.+assert :: Prop -> PatchCheck Bool+assert p = handle_inconsistent False $ do+ P ks nots <- get+ if has p nots+ then do+ put Inconsistent+ return False+ else if has p ks+ then return True+ else do+ put (P (p:ks) nots)+ return True++-- | Like @assert@, but negatively: state that some property must not hold for+-- the current repo.+assert_not :: Prop -> PatchCheck Bool+assert_not p = handle_inconsistent False $ do+ P ks nots <- get+ if has p ks+ then do+ put Inconsistent+ return False+ else if has p nots+ then return True+ else do+ put (P ks (p:nots))+ return True++-- | Remove a property from the list of properties that do not hold for this+-- repo (if it's there), and add it to the list of properties that hold.+-- Returns False if the repo is inconsistent, True otherwise.+change_to_true :: Prop -> PatchCheck Bool+change_to_true p = handle_inconsistent False $ do+ modify (\(P ks nots) -> P (p:ks) (filter (p /=) nots))+ return True++-- | Remove a property from the list of properties that hold for this repo (if+-- it's in there), and add it to the list of properties that do not hold.+-- Returns False if the repo is inconsistent, True otherwise.+change_to_false :: Prop -> PatchCheck Bool+change_to_false p = handle_inconsistent False $ do+ modify (\(P ks nots) -> P (filter (p /=) ks) (p:nots))+ return True++assert_file_exists :: String -> PatchCheck Bool+assert_file_exists f = do assert_not $ NotEx f+ assert_not $ DirEx f+ assert $ FileEx f+assert_dir_exists :: String -> PatchCheck Bool+assert_dir_exists d = do assert_not $ NotEx d+ assert_not $ FileEx d+ assert $ DirEx d+assert_exists :: String -> PatchCheck Bool+assert_exists f = assert_not $ NotEx f++assert_no_such :: String -> PatchCheck Bool+assert_no_such f = do assert_not $ FileEx f+ assert_not $ DirEx f+ assert $ NotEx f++create_file :: String -> PatchCheck Bool+create_file fn = do+ superdirs_exist fn+ assert_no_such fn+ change_to_true (FileEx fn)+ change_to_false (NotEx fn)++create_dir :: String -> PatchCheck Bool+create_dir fn = do+ substuff_dont_exist fn+ superdirs_exist fn+ assert_no_such fn+ change_to_true (DirEx fn)+ change_to_false (NotEx fn)++remove_file :: String -> PatchCheck Bool+remove_file fn = do+ superdirs_exist fn+ assert_file_exists fn+ file_empty fn+ change_to_false (FileEx fn)+ change_to_true (NotEx fn)++remove_dir :: String -> PatchCheck Bool+remove_dir fn = do+ substuff_dont_exist fn+ superdirs_exist fn+ assert_dir_exists fn+ change_to_false (DirEx fn)+ change_to_true (NotEx fn)++check_move :: String -> String -> PatchCheck Bool+check_move f f' = do+ superdirs_exist f+ superdirs_exist f'+ assert_exists f+ assert_no_such f'+ do_swap f f'++substuff_dont_exist :: String -> PatchCheck Bool+substuff_dont_exist d = handle_inconsistent False $ do+ P ks _ <- get+ if all noss ks+ then return True+ else do+ put Inconsistent+ return False+ where noss (FileEx f) = not (is_within_dir f)+ noss (DirEx f) = not (is_within_dir f)+ noss _ = True+ is_within_dir f = (d ++ "/") `isPrefixOf` f++-- the init and tail calls dump the final init (which is just the path itself+-- again), the first init (which is empty), and the initial "." from+-- splitDirectories+superdirs_exist :: String -> PatchCheck Bool+superdirs_exist fn = and `fmap` mapM assert_dir_exists superdirs+ where superdirs = map (("./"++) . joinPath) + (init (tail (inits (tail (splitDirectories fn)))))++file_exists :: String -> PatchCheck Bool+file_exists fn = do+ superdirs_exist fn+ assert_file_exists fn++dir_exists :: String -> PatchCheck Bool+dir_exists fn = do+ superdirs_exist fn+ assert_dir_exists fn
@@ -0,0 +1,292 @@+-- Copyright (C) 2002-2004 David Roundy+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.++{-# OPTIONS_GHC -cpp -fglasgow-exts #-}+{-# LANGUAGE CPP #-}+-- , TypeOperators, GeneralizedNewtypeDeriving, ScopedTypeVariables #-}++#include "gadts.h"++-- | PatchChoices divides a sequence of patches into three sets: "first",+-- "middle" and "last", such that all patches can be applied, if you first+-- apply the first ones then the middle ones and then the last ones.+-- Obviously if there are dependencies between the patches that will put a+-- constraint on how you can choose to divide them up. The PatchChoices data+-- type and associated functions are here to deal with many of the common+-- cases that come up when choosing a subset of a group of patches.+--+-- 'force_last' tells PatchChoices that a particular patch is required to be in+-- the "last" group, which also means that any patches that depend on it+-- must be in the "last" group.+--+-- Internally, a PatchChoices doesn't actually reorder the patches until it is+-- asked for the final output (e.g. by 'get_first_choice'). Instead, each+-- patch is placed in a state of definitely first, definitely last and+-- undecided; undecided leans towards "middle". In case you're wondering+-- about the first-middle-last language, it's because in some cases the+-- "yes" answers will be last (as is the case for the revert command), and+-- in others first (as in record, pull and push).+module Darcs.Patch.Choices ( PatchChoices, patch_choices, patch_choices_tps,+ patch_slot,+ get_choices,+ separate_first_middle_from_last,+ separate_first_from_middle_last,+ force_first, force_firsts, force_last, force_lasts,+ force_matching_first, force_matching_last,+ select_all_middles,+ make_uncertain, make_everything_later,+ TaggedPatch, Tag, tag, tp_patch,+ Slot(..),+ ) where++import System.IO.Unsafe ( unsafePerformIO )+import Data.IORef ( newIORef, writeIORef, readIORef )+import Darcs.Patch+import Darcs.Patch.Permutations ( commuteWhatWeCanRL )+import Darcs.Patch.Patchy ( Invert, Commute )+import Darcs.Ordered ( FL(..), RL(..), MyEq, unsafeCompare,+ (:>)(..), (:\/:)(..), (:/\:)(..),+ zipWithFL, mapFL_FL, mapFL,+ (+>+), reverseRL, unsafeCoerceP )+++newtype Tag = TG Integer deriving ( Num, Show, Eq, Ord, Enum )+data TaggedPatch p C(x y) = TP Tag (p C(x y))+data PatchChoice p C(x y) = PC (TaggedPatch p C(x y)) Slot+newtype PatchChoices p C(x y) = PCs (FL (PatchChoice p) C(x y))++data Slot = InFirst | InMiddle | InLast++invertTag :: Slot -> Slot+invertTag InFirst = InLast+invertTag InLast = InFirst+invertTag t = t++tag :: TaggedPatch p C(x y) -> Tag+tag (TP (TG t) _) = TG t++tp_patch :: TaggedPatch p C(x y) -> p C(x y)+tp_patch (TP _ p) = p++liftTP :: (p C(x y) -> p C(a b)) -> (TaggedPatch p C(x y) -> TaggedPatch p C(a b))+liftTP f (TP t p) = TP t (f p)++instance MyEq p => MyEq (TaggedPatch p) where+ unsafeCompare (TP t1 p1) (TP t2 p2) = t1 == t2 && unsafeCompare p1 p2++instance Invert p => Invert (TaggedPatch p) where+ invert = liftTP invert+ identity = TP (-1) identity++instance Commute p => Commute (TaggedPatch p) where+ commute (TP t1 p1 :> TP t2 p2) = do p2' :> p1' <- commute (p1 :> p2)+ return (TP t2 p2' :> TP t1 p1')+ list_touched_files (TP _ p) = list_touched_files p+ merge (TP t1 p1 :\/: TP t2 p2) = case merge (p1 :\/: p2) of+ p2' :/\: p1' -> TP t2 p2' :/\: TP t1 p1'++patch_choices :: Patchy p => FL p C(x y) -> PatchChoices p C(x y)+patch_choices = fst . patch_choices_tps++patch_choices_tps :: Patchy p => FL p C(x y) -> (PatchChoices p C(x y), FL (TaggedPatch p) C(x y))+patch_choices_tps ps = let tps = zipWithFL TP [1..] ps+ in (PCs $ zipWithFL (flip PC) (repeat InMiddle) tps, tps)++make_everything_later :: Patchy p => PatchChoices p C(x y) -> PatchChoices p C(x y)++instance MyEq p => MyEq (PatchChoice p) where+ unsafeCompare (PC tp1 _) (PC tp2 _) = unsafeCompare tp1 tp2++instance Invert p => Invert (PatchChoice p) where+ invert (PC tp mf) = PC (invert tp) (invertTag mf)+ identity = PC identity InMiddle++instance Commute p => Commute (PatchChoice p) where+ commute (PC t1 x1 :> PC t2 x2)+ = do t2' :> t1' <- commute (t1 :> t2)+ return (PC t2' x2 :> PC t1' x1)+ merge (PC t1 x1 :\/: PC t2 x2)+ = case merge (t1 :\/: t2) of+ t2' :/\: t1' -> PC t2' x2 :/\: PC t1' x1+ list_touched_files (PC t _) = list_touched_files t++invertSeq :: (Invert p, Invert q) => (p :> q) C(x y) -> (q :> p) C(y x)+invertSeq (x :> y) = (invert y :> invert x)++separate_first_from_middle_last :: Patchy p => PatchChoices p C(x z)+ -> (FL (TaggedPatch p) :> FL (TaggedPatch p)) C(x z)+separate_first_from_middle_last (PCs e) = pull_only_firsts e++separate_first_middle_from_last :: Patchy p => PatchChoices p C(x z)+ -> (FL (TaggedPatch p) :> FL (TaggedPatch p)) C(x z)+separate_first_middle_from_last (PCs e) = pull_firsts_middles e++get_choices :: Patchy p => PatchChoices p C(x y)+ -> (FL (TaggedPatch p) :> FL (TaggedPatch p) :> FL (TaggedPatch p)) C(x y)+get_choices (PCs e) = case pull_firsts e of+ f :> ml -> case pull_firsts (invert ml) of+ l :> m -> f :> mapFL_FL pc2tp (invert m) :> invert l+ where pc2tp (PC tp _) = tp++pull_firsts_middles :: Patchy p => FL (PatchChoice p) C(x z) -> (FL (TaggedPatch p) :> FL (TaggedPatch p)) C(x z)+pull_firsts_middles easyPC =+ let r = unsafePerformIO+ $ newIORef (error "pull_firsts_middles called badly")+ f :: Patchy p => RL (TaggedPatch p) C(a x) -> FL (PatchChoice p) C(x z) -> FL (TaggedPatch p) C(a d)+ f acc NilFL = unsafePerformIO (writeIORef r (reverseRL acc)) `seq` (unsafeCoerceP NilFL)+ f acc (PC tp InLast:>:e) = f (tp:<:acc) e+ f acc (PC tp _:>:e) = case commuteWhatWeCanRL (acc :> tp) of+ more :> tp' :> acc' -> reverseRL more+>+tp':>:f acc' e+ xs = f NilRL easyPC+ in (xs :> unsafePerformIO (readIORef r))++pull_only_firsts :: Patchy p => FL (PatchChoice p) C(x z) -> (FL (TaggedPatch p) :> FL (TaggedPatch p)) C(x z)+pull_only_firsts easyPC =+ let r = unsafePerformIO+ $ newIORef (error "pull_only_firsts called badly")+ f :: Patchy p => RL (TaggedPatch p) C(a x) -> FL (PatchChoice p) C(x z) -> FL (TaggedPatch p) C(a d)+ f acc NilFL = unsafePerformIO (writeIORef r (reverseRL acc)) `seq` (unsafeCoerceP NilFL)+ f acc (PC tp InFirst:>:e) = case commuteWhatWeCanRL (acc :> tp) of+ more :> tp' :> acc' -> reverseRL more+>+tp':>:f acc' e+ f acc (PC tp _:>:e) = f (tp:<:acc) e+ xs = f NilRL easyPC+ in (xs :> unsafePerformIO (readIORef r))++{-+pull_middles_lasts :: EasyPC p -> ([TaggedPatch p], [TaggedPatch p])+pull_middles_lasts easyPC =+ let r = unsafePerformIO+ $ newIORef (error "pull_middles_lasts called badly")+ f acc [] = unsafePerformIO (writeIORef r (reverse acc)) `seq` []+ f acc (PC tp (Just True):e) = f (tp:acc) e+ f acc (PC (TP t p) _:e) = case commute_up_list p acc of+ (acc', p') -> TP t p':f acc' e+ xs = f [] easyPC+ in (xs, unsafePerformIO (readIORef r))+-}++--pull_only_lasts :: EasyPC p -> ([TaggedPatch p], [TaggedPatch p])+--pull_only_lasts easyPC =+-- let r = unsafePerformIO+-- $ newIORef (error "pull_only_lasts called badly")+-- f acc [] = unsafePerformIO (writeIORef r (reverse acc)) `seq` []+-- f acc (PC (TP t p) (Just False):e) = case commute_up_list p acc of+-- (acc', p') -> TP t p':f acc' e+-- f acc (PC tp _:e) = f (tp:acc) e+-- xs = f [] easyPC+-- in (xs, unsafePerformIO (readIORef r))++pull_firsts :: Patchy p => FL (PatchChoice p) C(x z) -> (FL (TaggedPatch p) :> FL (PatchChoice p)) C(x z)+pull_firsts e = case pull_first e of+ Nothing -> (NilFL :> e)+ Just (p:>e') -> case pull_firsts e' of+ (ps:>e'') -> (p:>:ps :> e'')++pull_lasts :: Patchy p => FL (PatchChoice p) C(x y) -> (FL (PatchChoice p) :> FL (TaggedPatch p)) C(x y)+pull_lasts e = invertSeq $ pull_firsts $ invert e++pull_first :: Patchy p => FL (PatchChoice p) C(x z) -> Maybe ((TaggedPatch p :> FL (PatchChoice p)) C(x z))+pull_first NilFL = Nothing+pull_first (PC tp InFirst:>:e) = Just (tp :> e)+pull_first (PC (TP t p) InLast:>:e) =+ case pull_first e of+ Just (TP t2 p2 :> e') ->+ case commute (p:>p2) of+ Just (p2':>p') -> Just (TP t2 p2' :> PC (TP t p') InLast:>:e')+ Nothing -> error "Aaack fixme!"+ Nothing -> Nothing+pull_first (PC tp@(TP t p) InMiddle:>:e) =+ case pull_first e of+ Just (TP t2 p2 :> e') ->+ case commute (p:>p2) of+ Just (p2':>p') -> Just (TP t2 p2' :> (PC (TP t p') InMiddle:>:e'))+ Nothing -> Just (tp :> PC (TP (-t2) p2) InFirst:>:e')+ Nothing -> Nothing++patch_slot :: forall p C(a b x y). TaggedPatch p C(a b) -> PatchChoices p C(x y) -> Slot+patch_slot tp (PCs e) = ipf e+ where ipf :: FL (PatchChoice p) C(u v) -> Slot+ ipf (PC a mb:>:e') | tag a == tag tp = mb+ | otherwise = ipf e'+ -- actually, the following should be impossible, but this is a reasonable answer+ ipf NilFL = InLast++set_simplys :: [Tag] -> Bool -> FL (PatchChoice p) C(x y) -> FL (PatchChoice p) C(x y)+set_simplys ts b e = mapFL_FL ch e+ where ch (PC tp@(TP t _) _)+ | t `elem` ts = PC tp (if b then InFirst else InLast)+ | otherwise = PC tp InMiddle+++m2ids :: (FORALL(x y) TaggedPatch p C(x y) -> Bool) -> FL (PatchChoice p) C(a b) -> [Tag]+m2ids m (PC tp@(TP t _) _:>:e)+ | m tp = t:m2ids m e+ | otherwise = m2ids m e+m2ids _ NilFL = []++force_matching_first :: Patchy p => (FORALL(x y) TaggedPatch p C(x y) -> Bool)+ -> PatchChoices p C(a b) -> PatchChoices p C(a b)+force_matching_first m (PCs e) =+ let thd (PC (TP t _) _) = t+ xs = m2ids m e+ not_needed = case pull_firsts $ set_simplys xs True e of+ _ :> rest -> mapFL thd rest+ ch pc@(PC tp@(TP t _) _)+ | t `elem` not_needed = pc+ | otherwise = PC tp InFirst+ in PCs $ mapFL_FL ch e++force_firsts :: Patchy p => [Tag] -> PatchChoices p C(x y) -> PatchChoices p C(x y)+force_firsts ps pc = force_matching_first ((`elem` ps) . tag) pc++force_first :: Patchy p => Tag -> PatchChoices p C(x y) -> PatchChoices p C(x y)+force_first p pc = force_matching_first ((== p) . tag) pc++select_all_middles :: Patchy p => Bool -> PatchChoices p C(x y) -> PatchChoices p C(x y)+select_all_middles b (PCs e) = PCs (mapFL_FL f e)+ where f (PC tp InMiddle) = PC tp (if b then InLast else InFirst)+ f pc = pc++reverse_pc :: Patchy p => PatchChoices p C(x y) -> PatchChoices p C(y x)+reverse_pc (PCs e) = PCs $ invert e++force_matching_last :: Patchy p => (FORALL(x y) TaggedPatch p C(x y) -> Bool)+ -> PatchChoices p C(a b) -> PatchChoices p C(a b)+force_matching_last m (PCs e) =+ let thd (PC (TP t _) _) = t+ xs = m2ids m e+ not_needed = case pull_lasts $ set_simplys xs False e of+ rest :> _ -> mapFL thd rest+ ch pc@(PC tp@(TP t _) _)+ | t `elem` not_needed = pc+ | otherwise = PC tp InLast+ in PCs $ mapFL_FL ch e++force_last :: Patchy p => Tag -> PatchChoices p C(x y) -> PatchChoices p C(x y)+force_last p pc = reverse_pc $ force_first p $ reverse_pc pc++force_lasts :: Patchy p => [Tag] -> PatchChoices p C(x y) -> PatchChoices p C(x y)+force_lasts ps pc = reverse_pc $ force_firsts ps $ reverse_pc pc++make_uncertain :: Patchy p => Tag -> PatchChoices p C(x y) -> PatchChoices p C(x y)+make_uncertain t (PCs e) = PCs $ mapFL_FL ch e+ where ch pc@(PC x _) = if t == tag x then PC x InMiddle else pc++make_everything_later (PCs e) = PCs $ mapFL_FL ch e+ where ch (PC tp InMiddle) = PC tp InLast+ ch (PC tp InFirst) = PC tp InMiddle+ ch x = x
@@ -0,0 +1,879 @@+% Copyright (C) 2002-2003 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.+++\begin{code}+{-# OPTIONS_GHC -cpp -fglasgow-exts -fno-warn-orphans #-}+{-# LANGUAGE CPP #-}+-- , TypeOperators #-}++#include "gadts.h"++module Darcs.Patch.Commute ( fromPrims,+ modernize_patch,+#ifndef GADT_WITNESSES+ merge, elegant_merge,+ merger, unravel,+#endif+ public_unravel, mangle_unravelled,+ CommuteFunction, Perhaps(..),+ -- for other commutes:+ toMaybe,+ )+ where++import Control.Monad ( MonadPlus, mplus, msum, mzero )++import Darcs.Patch.FileName ( FileName, fn2fp, fp2fn )+import Darcs.Patch.Info ( invert_name, idpatchinfo )+import Darcs.Patch.Patchy ( Commute(..), Invert(..) )+import Darcs.Patch.Core ( Patch(..), Named(..),+#ifndef GADT_WITNESSES+ flattenFL,+ is_merger,+#endif+ merger_undo,+ join_patchesFL )+import Darcs.Patch.Prim ( Prim(..), FromPrims(..),+ Conflict(..), Effect(..),+ is_filepatch, sort_coalesceFL,+#ifndef GADT_WITNESSES+ FilePatchType(..), DirPatchType(..),+#else+ FilePatchType(Hunk),+#endif+ is_hunk, modernizePrim )+import qualified Data.ByteString.Char8 as BC (pack, last)+import qualified Data.ByteString as B (null, ByteString)+import Data.Maybe ( isJust )+import Data.List ( intersperse, sort )+#ifndef GADT_WITNESSES+import Darcs.Patch.Permutations ( head_permutationsRL, head_permutationsFL )+import Printer ( text, vcat, ($$) )+import Darcs.Patch.Patchy ( invertRL )+import Darcs.Patch.Show ( showPatch_ )+import Data.List ( nubBy )+import Darcs.Sealed ( unsafeUnseal )+#endif+import Darcs.Utils ( nubsort )+#include "impossible.h"+import Darcs.Sealed ( Sealed(..), mapSeal )+import Darcs.Ordered ( mapFL, mapFL_FL, unsafeCoerceP,+ FL(..), RL(..),+ (:/\:)(..), (:<)(..), (:\/:)(..), (:>)(..),+#ifndef GADT_WITNESSES+ lengthFL, mapRL,+#endif+ reverseFL, reverseRL, concatFL,+ MyEq, unsafeCompare+ )++--import Darcs.ColorPrinter ( traceDoc )+--import Printer ( greenText )+\end{code}+ +\section{Commuting patches}++\subsection{Composite patches}++Composite patches are made up of a series of patches intended to be applied+sequentially. They are represented by a list of patches, with the first+patch in the list being applied first.++\newcommand{\commutex}{\longleftrightarrow}+\newcommand{\commutes}{\longleftrightarrow}++The first way (of only two) to change the context of a patch is by+commutation, which is the process of changing the order of two sequential+patches.+\begin{dfn}+The commutation of patches $P_1$ and $P_2$ is represented by+\[ P_2 P_1 \commutes {P_1}' {P_2}'. \]+Here $P_1'$ is intended to describe the same change as $P_1$, with the+only difference being that $P_1'$ is applied after $P_2'$ rather than+before $P_2$.+\end{dfn}+The above definition is obviously rather vague, the reason being that what+is the ``same change'' has not been defined, and we simply assume (and+hope) that the code's view of what is the ``same change'' will match those+of its human users. The `$\commutes$' operator should be read as something+like the $==$ operator in C, indicating that the right hand side performs+identical changes to the left hand side, but the two patches are in+reversed order. When read in this manner, it is clear that commutation+must be a reversible process, and indeed this means that commutation+\emph{can} fail, and must fail in certain cases. For example, the creation+and deletion of the same file cannot be commuted. When two patches fail to+commutex, it is said that the second patch depends on the first, meaning+that it must have the first patch in its context (remembering that the+context of a patch is a set of patches, which is how we represent a tree).+\footnote{The fact that commutation can fail makes a huge difference in the+whole patch formalism. It may be possible to create a formalism in which+commutation always succeeds, with the result of what would otherwise be a+commutation that fails being something like a virtual particle (which can+violate conservation of energy), and it may be that such a formalism would+allow strict mathematical proofs (whereas those used in the current+formalism are mostly only hand waving ``physicist'' proofs). However, I'm+not sure how you'd deal with a request to delete a file that has not yet+been created, for example. Obviously you'd need to create some kind of+antifile, which would annihilate with the file when that file finally got+created, but I'm not entirely sure how I'd go about doing this.+$\ddot\frown$ So I'm sticking with my hand waving formalism.}++%I should add that one using the inversion relationship of sequential+%patches, one can avoid having to provide redundant definitions of+%commutation.++% There is another interesting property which is that a commutex's results+% can't be affected by commuting another thingamabopper.++\begin{code}+data Perhaps a = Unknown | Failed | Succeeded a++instance Monad Perhaps where+ (Succeeded x) >>= k = k x+ Failed >>= _ = Failed+ Unknown >>= _ = Unknown+ Failed >> _ = Failed+ (Succeeded _) >> k = k+ Unknown >> k = k+ return = Succeeded+ fail _ = Unknown++instance MonadPlus Perhaps where+ mzero = Unknown+ Unknown `mplus` ys = ys+ Failed `mplus` _ = Failed+ (Succeeded x) `mplus` _ = Succeeded x++toMaybe :: Perhaps a -> Maybe a+toMaybe (Succeeded x) = Just x+toMaybe _ = Nothing++toPerhaps :: Maybe a -> Perhaps a+toPerhaps (Just x) = Succeeded x+toPerhaps Nothing = Failed++#ifndef GADT_WITNESSES+clever_commute :: CommuteFunction -> CommuteFunction+clever_commute c (p1:<p2) =+ case c (p1 :< p2) of+ Succeeded x -> Succeeded x+ Failed -> Failed+ Unknown -> case c (invert p2 :< invert p1) of+ Succeeded (p1' :< p2') -> Succeeded (invert p2' :< invert p1')+ Failed -> Failed+ Unknown -> Unknown+#endif++speedy_commute :: CommuteFunction+speedy_commute (p1 :< p2) -- Deal with common case quickly!+ | p1_modifies /= Nothing && p2_modifies /= Nothing &&+ p1_modifies /= p2_modifies = Succeeded (unsafeCoerceP p2 :< unsafeCoerceP p1)+ | otherwise = Unknown+ where p1_modifies = is_filepatch_merger p1+ p2_modifies = is_filepatch_merger p2++instance Commute p => Commute (Named p) where+ commute (NamedP n1 d1 p1 :> NamedP n2 d2 p2) =+ if n2 `elem` d1 || n1 `elem` d2+ then Nothing+ else do (p2' :> p1') <- commute (p1 :> p2)+ return (NamedP n2 d2 p2' :> NamedP n1 d1 p1')+ merge (NamedP n1 d1 p1 :\/: NamedP n2 d2 p2)+ = case merge (p1 :\/: p2) of+ (p2' :/\: p1') -> NamedP n2 d2 p2' :/\: NamedP n1 d1 p1'+ list_touched_files (NamedP _ _ p) = list_touched_files p++instance Conflict p => Conflict (Named p) where+ list_conflicted_files (NamedP _ _ p) = list_conflicted_files p+ resolve_conflicts (NamedP _ _ p) = resolve_conflicts p++everything_else_commute :: MaybeCommute -> CommuteFunction+everything_else_commute c x = eec x+ where+ eec :: CommuteFunction+ eec (PP px :< PP py) = toPerhaps $ do y' :< x' <- commutex (px :< py)+ return (PP y' :< PP x')+ eec (ComP NilFL :< p1) = Succeeded (unsafeCoerceP p1 :< (ComP NilFL))+ eec (p2 :< ComP NilFL) = Succeeded (ComP NilFL :< unsafeCoerceP p2)+ eec (ComP (p:>:ps) :< p1) = toPerhaps $ do+ (p1' :< p') <- c (p :< p1)+ (p1'' :< ComP ps') <- c (ComP ps :< p1')+ return (p1'' :< ComP (p':>:ps'))+ eec (patch2 :< ComP patches) =+ toPerhaps $ do (patches' :< patch2') <- ccr (patch2 :< reverseFL patches)+ return (ComP (reverseRL patches') :< patch2')+ where ccr :: FORALL(x y) (Patch :< RL Patch) C(x y) -> Maybe ((RL Patch :< Patch) C(x y))+ ccr (p2 :< NilRL) = seq p2 $ return (NilRL :< p2)+ ccr (p2 :< p:<:ps) = do (p' :< p2') <- c (p2 :< p)+ (ps' :< p2'') <- ccr (p2' :< ps)+ return (p':<:ps' :< p2'')+ eec _xx =+ msum [+#ifndef GADT_WITNESSES+ clever_commute commute_recursive_merger _xx+ ,clever_commute other_commute_recursive_merger _xx+#endif+ ]++{-+Note that it must be true that++commutex (A^-1 A, P) = Just (P, A'^-1 A')++and++if commutex (A, B) == Just (B', A')+then commutex (B^-1, A^-1) == Just (A'^-1, B'^-1)+-}++#ifndef GADT_WITNESSES+merger_commute :: (Patch :< Patch) -> Perhaps (Patch :< Patch)+merger_commute (Merger _ _ p1 p2 :< pA)+ | unsafeCompare pA p1 = Succeeded (merger "0.0" p2 p1 :< p2)+ | unsafeCompare pA (invert (merger "0.0" p2 p1)) = Failed+merger_commute (Merger _ _+ (Merger _ _ c b)+ (Merger _ _ c' a) :<+ Merger _ _ b' c'')+ | unsafeCompare b' b && unsafeCompare c c' && unsafeCompare c c'' =+ Succeeded (merger "0.0" (merger "0.0" b a) (merger "0.0" b c) :<+ merger "0.0" b a)+merger_commute _ = Unknown+#endif++instance Commute Patch where+ merge (y :\/: z) =+#ifndef GADT_WITNESSES+ case actual_merge (y:\/:z) of+ y' -> case commutex (y' :< z) of+ Nothing -> bugDoc $ text "merge_patches bug"+ $$ showPatch_ y+ $$ showPatch_ z+ $$ showPatch_ y'+ Just (z' :< _) -> z' :/\: y'+#else+ case elegant_merge (y:\/:z) of+ Just (z' :/\: y') -> z' :/\: y'+ Nothing -> undefined+#endif+ commutex x = toMaybe $ msum [speedy_commute x,+#ifndef GADT_WITNESSES+ clever_commute merger_commute x,+#endif+ everything_else_commute commutex x+ ]+ -- Recurse on everything, these are potentially spoofed patches+ list_touched_files (ComP ps) = nubsort $ concat $ mapFL list_touched_files ps+ list_touched_files (Merger _ _ p1 p2) = nubsort $ list_touched_files p1+ ++ list_touched_files p2+ list_touched_files c@(Regrem _ _ _ _) = list_touched_files $ invert c+ list_touched_files (PP p) = list_touched_files p++commute_no_merger :: MaybeCommute+commute_no_merger x =+#ifndef GADT_WITNESSES+ toMaybe $ msum [speedy_commute x,+ everything_else_commute commute_no_merger x]+#else+ bug "commute_no_merger undefined when compiled with GADTs" x+#endif++is_filepatch_merger :: Patch C(x y) -> Maybe FileName+is_filepatch_merger (PP p) = is_filepatch p+is_filepatch_merger (Merger _ _ p1 p2) = do+ f1 <- is_filepatch_merger p1+ f2 <- is_filepatch_merger p2+ if f1 == f2 then return f1 else Nothing+is_filepatch_merger (Regrem und unw p1 p2)+ = is_filepatch_merger (Merger und unw p1 p2)+is_filepatch_merger (ComP _) = Nothing++#ifndef GADT_WITNESSES+commute_recursive_merger :: (Patch :< Patch) -> Perhaps (Patch :< Patch)+commute_recursive_merger (p@(Merger _ _ p1 p2) :< pA) = toPerhaps $+ do (pA' :< _) <- commutex (undo :< pA)+ commutex (invert undo :< pA')+ (pAmid :< _) <- commutex (invert p1 :< pA)+ (pAx :< p1') <- commutex (p1 :< pAmid)+ assert (pAx `unsafeCompare` pA)+ (_ :<p2') <- commutex (p2 :< pAmid)+ (_ :< p2o) <- commutex (p2' :< invert pAmid)+ assert (p2o `unsafeCompare` p2)+ let p' = if unsafeCompare p1' p1 && unsafeCompare p2' p2+ then p+ else merger "0.0" p1' p2'+ undo' = merger_undo p'+ (_ :< pAo) <- commutex (pA' :< undo')+ assert (pAo `unsafeCompare` pA)+ return (pA' :< p')+ where undo = merger_undo p+commute_recursive_merger _ = Unknown++other_commute_recursive_merger :: (Patch :< Patch) -> Perhaps (Patch :< Patch)+other_commute_recursive_merger (pA':< p_old@(Merger _ _ p1' p2')) =+ toPerhaps $+ do (_ :< pA) <- commutex (pA' :< merger_undo p_old)+ (p1 :< pAmid) <- commutex (pA :< p1')+ (pAmido :< _) <- commutex (invert p1 :< pA)+ assert (pAmido `unsafeCompare` pAmid)+ (_ :< p2) <- commutex (p2' :< invert pAmid)+ (_ :< p2o') <- commutex (p2 :< pAmid)+ assert (p2o' `unsafeCompare` p2')+ let p = if p1 `unsafeCompare` p1' && p2 `unsafeCompare` p2'+ then p_old+ else merger "0.0" p1 p2+ undo = merger_undo p+ assert (not $ pA `unsafeCompare` p1) -- special case here...+ (pAo' :< _) <- commutex (undo :<pA)+ assert (pAo' `unsafeCompare` pA')+ return (p :< pA)+other_commute_recursive_merger _ = Unknown++assert :: Bool -> Maybe ()+assert False = Nothing+assert True = Just ()+#endif++type CommuteFunction = FORALL(x y) (Patch :< Patch) C(x y) -> Perhaps ((Patch :< Patch) C(x y))+type MaybeCommute = FORALL(x y) (Patch :< Patch) C(x y) -> Maybe ((Patch :< Patch) C(x y))+\end{code}++\paragraph{Merge}+\newcommand{\merge}{\Longrightarrow}+The second way one can change the context of a patch is by a {\bf merge}+operation. A merge is an operation that takes two parallel patches and+gives a pair of sequential patches. The merge operation is represented by+the arrow ``\( \merge \)''.+\begin{dfn}\label{merge_dfn}+The result of a merge of two patches, $P_1$ and $P_2$ is one of two patches,+$P_1'$ and $P_2'$, which satisfy the relationship:+\[ P_2 \parallel P_1 \merge {P_2}' P_1 \commutex {P_1}' P_2. \]+\end{dfn}+Note that the sequential patches resulting from a merge are \emph{required}+to commutex. This is an important consideration, as without it most of the+manipulations we would like to perform would not be possible. The other+important fact is that a merge \emph{cannot fail}. Naively, those two+requirements seem contradictory. In reality, what it means is that the+result of a merge may be a patch which is much more complex than any we+have yet considered\footnote{Alas, I don't know how to prove that the two+constraints even \emph{can} be satisfied. The best I have been able to do+is to believe that they can be satisfied, and to be unable to find an case+in which my implementation fails to satisfy them. These two requirements+are the foundation of the entire theory of patches (have you been counting+how many foundations it has?).}.++\subsection{How merges are actually performed}++The constraint that any two compatible patches (patches which can+successfully be applied to the same tree) can be merged is actually quite+difficult to apply. The above merge constraints also imply that the result+of a series of merges must be independent of the order of the merges. So+I'm putting a whole section here for the interested to see what algorithms+I use to actually perform the merges (as this is pretty close to being the+most difficult part of the code).++The first case is that in which the two merges don't actually conflict, but+don't trivially merge either (e.g.\ hunk patches on the same file, where the+line number has to be shifted as they are merged). This kind of merge can+actually be very elegantly dealt with using only commutation and inversion.++There is a handy little theorem which is immensely useful when trying to+merge two patches.+\begin{thm}\label{merge_thm}+$ P_2' P_1 \commutex P_1' P_2 $ if and only if $ P_1'^{ -1}+P_2' \commutex P_2 P_1^{ -1} $, provided both commutations succeed. If+either commutex fails, this theorem does not apply.+\end{thm}+This can easily be proven by multiplying both sides of the first+commutation by $P_1'^{ -1}$ on the left, and by $P_1^{ -1}$ on the right.+Besides being used in merging, this theorem is also useful in the recursive+commutations of mergers. From Theorem~\ref{merge_thm}, we see that the+merge of $P_1$ and $P_2'$ is simply the commutation of $P_2$ with $P_1^{+-1}$ (making sure to do the commutation the right way). Of course, if this+commutation fails, the patches conflict. Moreover, one must check that the+merged result actually commutes with $P_1$, as the theorem applies only+when \emph{both} commutations are successful.++\begin{code}++elegant_merge :: (Patch :\/: Patch) C(x y)+ -> Maybe ((Patch :/\: Patch) C(x y))+elegant_merge (p1 :\/: p2) =+ case commutex (p1 :< invert p2) of+ Just (ip2':<p1') -> case commutex (p1' :< p2) of+ Nothing -> Nothing -- should be a redundant check+ Just (_:<p1o) -> if unsafeCompare p1o p1+ then Just (invert ip2' :/\: p1')+ else Nothing+ Nothing -> Nothing++\end{code}++Of course, there are patches that actually conflict, meaning a merge where+the two patches truly cannot both be applied (e.g.\ trying to create a file+and a directory with the same name). We deal with this case by creating a+special kind of patch to support the merge, which we will call a+``merger''. Basically, a merger is a patch that contains the two patches+that conflicted, and instructs darcs basically to resolve the conflict. By+construction a merger will satisfy the commutation property (see+Definition~\ref{merge_dfn}) that characterizes all merges. Moreover the+merger's properties are what makes the order of merges unimportant (which+is a rather critical property for darcs as a whole).++The job of a merger is basically to undo the two conflicting patches, and+then apply some sort of a ``resolution'' of the two instead. In the case+of two conflicting hunks, this will look much like what CVS does, where it+inserts both versions into the file. In general, of course, the two+conflicting patches may both be mergers themselves, in which case the+situation is considerably more complicated.++\begin{code}+#ifndef GADT_WITNESSES+actual_merge :: (Patch :\/: Patch) -> Patch+actual_merge (ComP the_p1s :\/: ComP the_p2s) =+ join_patchesFL $ mc the_p1s the_p2s+ where mc :: FL Patch -> FL Patch -> FL Patch+ mc NilFL (_:>:_) = NilFL+ mc p1s NilFL = p1s+ mc p1s (p2:>:p2s) = mc (merge_patches_after_patch p1s p2) p2s+actual_merge (ComP p1s :\/: p2) = seq p2 $+ join_patchesFL $ merge_patches_after_patch p1s p2+actual_merge (p1 :\/: ComP p2s) = seq p1 $ merge_patch_after_patches p1 p2s++actual_merge (p1 :\/: p2) = case elegant_merge (p1:\/:p2) of+ Just (_ :/\: p1') -> p1'+ Nothing -> merger "0.0" p2 p1++merge_patch_after_patches :: Patch -> FL Patch -> Patch+merge_patch_after_patches p (p1:>:p1s) =+ merge_patch_after_patches (actual_merge (p:\/:p1)) p1s+merge_patch_after_patches p NilFL = p++merge_patches_after_patch :: FL Patch -> Patch -> FL Patch+merge_patches_after_patch p2s p =+ case commutex (merge_patch_after_patches p p2s :< join_patchesFL p2s) of+ Just (ComP p2s':< _) -> p2s'+ _ -> impossible+#endif+\end{code}++Much of the merger code depends on a routine which recreates from a single+merger the entire sequence of patches which led up to that merger (this is,+of course, assuming that this is the complicated general case of a merger+of mergers of mergers). This ``unwind'' procedure is rather complicated,+but absolutely critical to the merger code, as without it we wouldn't even+be able to undo the effects of the patches involved in the merger, since we+wouldn't know what patches were all involved in it.++Basically, unwind takes a merger such as+\begin{verbatim}+M( M(A,B), M(A,M(C,D)))+\end{verbatim}+From which it recreates a merge history:+\begin{verbatim}+C+A+M(A,B)+M( M(A,B), M(A,M(C,D)))+\end{verbatim}+(For the curious, yes I can easily unwind this merger in my head [and on+paper can unwind insanely more complex mergers]---that's what comes of+working for a few months on an algorithm.) Let's start with a simple+unwinding. The merger \verb!M(A,B)! simply means that two patches+(\verb!A! and \verb!B!) conflicted, and of the two of them \verb!A! is+first in the history. The last two patches in the unwinding of any merger+are always just this easy. So this unwinds to:+\begin{verbatim}+A+M(A,B)+\end{verbatim}+What about a merger of mergers? How about \verb!M(A,M(C,D))!. In this case+we know the two most recent patches are:+\begin{verbatim}+A+M(A,M(C,D))+\end{verbatim}+But obviously the unwinding isn't complete, since we don't yet see where+\verb!C! and \verb!D! came from. In this case we take the unwinding of+\verb!M(C,D)! and drop its latest patch (which is \verb!M(C,D)! itself) and+place that at the beginning of our patch train:+\begin{verbatim}+C+A+M(A,M(C,D))+\end{verbatim}+As we look at \verb!M( M(A,B), M(A,M(C,D)))!, we consider the unwindings of+each of its subpatches:+\begin{verbatim}+ C+A A+M(A,B) M(A,M(C,D))+\end{verbatim}+As we did with \verb!M(A,M(C,D))!, we'll drop the first patch on the+right and insert the first patch on the left. That moves us up to the two+\verb!A!'s. Since these agree, we can use just one of them (they+``should'' agree). That leaves us with the \verb!C! which goes first.++The catch is that things don't always turn out this easily. There is no+guarantee that the two \verb!A!'s would come out at the same time, and if+they didn't, we'd have to rearrange things until they did. Or if there was+no way to rearrange things so that they would agree, we have to go on to+plan B, which I will explain now.++Consider the case of \verb!M( M(A,B), M(C,D))!. We can easily unwind the+two subpatches+\begin{verbatim}+A C+M(A,B) M(C,D)+\end{verbatim}+Now we need to reconcile the \verb!A! and \verb!C!. How do we do this?+Well, as usual, the solution is to use the most wonderful+Theorem~\ref{merge_thm}. In this case we have to use it in the reverse of+how we used it when merging, since we know that \verb!A! and \verb!C! could+either one be the \emph{last} patch applied before \verb!M(A,B)! or+\verb!M(C,D)!. So we can find \verb!C'! using+\[+A^{ -1} C \commutex C' A'^{ -1}+\]+Giving an unwinding of+\begin{verbatim}+C'+A+M(A,B)+M( M(A,B), M(C,D) )+\end{verbatim}+There is a bit more complexity to the unwinding process (mostly having to+do with cases where you have deeper nesting), but I think the general+principles that are followed are pretty much included in the above+discussion.++\begin{code}+#ifndef GADT_WITNESSES+unwind :: Patch -> RL Patch -- Recreates a patch history in reverse.+unwind (Merger _ unwindings _ _) = unwindings+unwind p = p :<: NilRL;++true_unwind :: Patch -> RL Patch -- Recreates a patch history in reverse.+true_unwind p@(Merger _ _ p1 p2) =+ case (unwind p1, unwind p2) of+ (_:<:p1s,_:<:p2s) -> p :<: p1 :<: reconcile_unwindings p p1s p2s+ _ -> impossible+true_unwind _ = impossible++reconcile_unwindings :: Patch -> RL Patch -> RL Patch -> RL Patch+reconcile_unwindings _ NilRL p2s = p2s+reconcile_unwindings _ p1s NilRL = p1s+reconcile_unwindings p (p1:<:p1s) p2s =+ case [(p1s', p2s')|+ p1s'@(hp1s':<:_) <- head_permutationsRL (p1:<:p1s),+ p2s'@(hp2s':<:_) <- head_permutationsRL p2s,+ hp1s' `unsafeCompare` hp2s'] of+ ((p1':<:p1s', _:<:p2s'):_) ->+ p1' :<: reconcile_unwindings p p1s' p2s'+ [] -> case reverseFL `fmap` put_before p1 (reverseRL p2s) of+ Just p2s' -> p1 :<: reconcile_unwindings p p1s p2s'+ Nothing ->+ case fmap reverseFL $ put_before (headRL p2s) $+ reverseRL (p1:<:p1s) of+ Just p1s' -> case p2s of+ hp2s:<:tp2s -> hp2s :<:+ reconcile_unwindings p p1s' tp2s+ NilRL -> impossible+ Nothing ->+ bugDoc $ text "in function reconcile_unwindings"+ $$ text "Original patch:"+ $$ showPatch_ p+ _ -> bug "in reconcile_unwindings"++put_before :: Patch -> FL Patch -> Maybe (FL Patch)+put_before p1 (p2:>:p2s) =+ do p2':<p1' <- commutex (invert p1:<p2)+ commutex (p1:<p2')+ (p2' :>:) `fmap` put_before p1' p2s+put_before _ NilFL = Just NilFL+#endif+\end{code}+ +\section{Conflicts}++There are a couple of simple constraints on the routine which determines+how to resolve two conflicting patches (which is called `glump'). These+must be satisfied in order that the result of a series of merges is always+independent of their order. Firstly, the output of glump cannot change+when the order of the two conflicting patches is switched. If it did, then+commuting the merger could change the resulting patch, which would be bad.+Secondly, the result of the merge of three (or more) conflicting patches+cannot depend on the order in which the merges are performed.++The conflict resolution code (glump) begins by ``unravelling'' the merger+into a set of sequences of patches. Each sequence of patches corresponds+to one non-conflicted patch that got merged together with the others. The+result of the unravelling of a series of merges must obviously be+independent of the order in which those merges are performed. This+unravelling code (which uses the unwind code mentioned above) uses probably+the second most complicated algorithm. Fortunately, if we can successfully+unravel the merger, almost any function of the unravelled merger satisfies+the two constraints mentioned above that the conflict resolution code must+satisfy.++\begin{code}+instance Conflict Patch where+ commute_no_conflicts (x:>y) = do x' :< y' <- commute_no_merger (y :< x)+ return (y':>x')+#ifndef GADT_WITNESSES+ resolve_conflicts patch = rcs NilFL $ reverseFL $ flattenFL patch+ where rcs :: FL Patch C(w y) -> RL Patch C(x y) -> [[Sealed (FL Prim C(w))]]+ rcs _ NilRL = []+ rcs passedby (p@(Merger _ _ _ _):<:ps) =+ case commute_no_merger (join_patchesFL passedby:<p) of+ Just (p'@(Merger _ _ p1 p2):<_) ->+ (map Sealed $ nubBy unsafeCompare $ effect (glump09 p1 p2) : unravel p')+ : rcs (p :>: passedby) ps+ Nothing -> rcs (p :>: passedby) ps+ _ -> impossible+ rcs passedby (p:<:ps) = seq passedby $+ rcs (p :>: passedby) ps+#else+ resolve_conflicts = bug "haven't defined resolve_conflicts with type witnesses."+#endif++public_unravel :: Patch C(x y) -> [Sealed (FL Prim C(y))]+#ifdef GADT_WITNESSES+public_unravel = bug "Haven't implemented public_unravel with type witnesses."+#else+public_unravel p = map Sealed $ unravel p+#endif++#ifndef GADT_WITNESSES+unravel :: Patch -> [FL Prim]+unravel p = nubBy unsafeCompare $+ map (sort_coalesceFL . concatFL . mapFL_FL effect) $+ get_supers $ map reverseRL $ new_ur p $ unwind p++get_supers :: [FL Patch] -> [FL Patch]+get_supers (x:xs) =+ case filter (not.(x `is_superpatch_of`)) xs of+ xs' -> if or $ map (`is_superpatch_of` x) xs'+ then get_supers xs'+ else x : get_supers xs'+get_supers [] = []+is_superpatch_of :: FL Patch -> FL Patch -> Bool+x `is_superpatch_of` y | lengthFL y > lengthFL x = False+x `is_superpatch_of` y = x `iso` y+ where iso :: FL Patch -> FL Patch -> Bool+ _ `iso` NilFL = True+ NilFL `iso` _ = False+ a `iso` (b:>:bs) =+ case filter ((`unsafeCompare` b) . headFL) $+ head_permutationsFL a of+ ((_:>:as):_) -> as `iso` bs+ [] -> False+ _ -> bug "bug in is_superpatch_of"++headFL :: FL a -> a+headFL (x:>:_) = x+headFL NilFL = bug "bad headFL"++merger :: String -> Patch -> Patch -> Patch+merger "0.0" p1 p2 = Merger undoit unwindings p1 p2+ where fake_p = Merger identity NilRL p1 p2+ unwindings = true_unwind fake_p+ p = Merger identity unwindings p1 p2+ undoit =+ case (is_merger p1, is_merger p2) of+ (True ,True ) -> join_patchesFL $ invertRL $ tailRL $ unwind p+ where tailRL (_:<:t) = t+ tailRL _ = impossible+ (False,False) -> invert p1+ (True ,False) -> join_patchesFL NilFL+ (False,True ) -> join_patchesFL (invert p1 :>: merger_undo p2 :>: NilFL)+merger g _ _ =+ error $ "Cannot handle mergers other than version 0.0\n"++g+ ++ "\nPlease use darcs optimize --modernize with an older darcs."++glump09 :: Patch -> Patch -> Patch+glump09 p1 p2 = fromPrims $ unsafeUnseal $ mangle_unravelled $ map Sealed $ unravel $ merger "0.0" p1 p2++#endif++mangle_unravelled :: [Sealed (FL Prim C(x))] -> Sealed (FL Prim C(x))+mangle_unravelled pss = if only_hunks pss+ then (:>: NilFL) `mapSeal` mangle_unravelled_hunks pss+ else head pss++only_hunks :: [Sealed (FL Prim C(x))] -> Bool+only_hunks [] = False+only_hunks pss = fn2fp f /= "" && all oh pss+ where f = get_a_filename pss+ oh :: Sealed (FL Prim C(x)) -> Bool+ oh (Sealed (p:>:ps)) = is_hunk p &&+ [fn2fp f] == list_touched_files p &&+ oh (Sealed ps)+ oh (Sealed NilFL) = True++apply_hunks :: [Maybe B.ByteString] -> FL Prim C(x y) -> [Maybe B.ByteString]+apply_hunks ms (FP _ (Hunk l o n):>:ps) = apply_hunks (rls l ms) ps+ where rls 1 mls = map Just n ++ drop (length o) mls+ rls i (ml:mls) = ml : rls (i-1) mls+ rls _ [] = bug "rls in apply_hunks"+apply_hunks ms NilFL = ms+apply_hunks _ (_:>:_) = impossible++get_old :: [Maybe B.ByteString] -> [Sealed (FL Prim C(x))] -> [Maybe B.ByteString]+get_old mls (ps:pss) = get_old (get_hunks_old mls ps) pss+get_old mls [] = mls++get_a_filename :: [Sealed (FL Prim C(x))] -> FileName+get_a_filename ((Sealed (FP f _:>:_)):_) = f+get_a_filename _ = fp2fn ""++get_hunks_old :: [Maybe B.ByteString] -> Sealed (FL Prim C(x))+ -> [Maybe B.ByteString]+get_hunks_old mls (Sealed ps) =+ apply_hunks (apply_hunks mls ps) (invert ps)++get_hunks_new :: [Maybe B.ByteString] -> Sealed (FL Prim C(x))+ -> [Maybe B.ByteString]+get_hunks_new mls (Sealed ps) = apply_hunks mls ps++get_hunkline :: [[Maybe B.ByteString]] -> Int+get_hunkline = ghl 1+ where ghl :: Int -> [[Maybe B.ByteString]] -> Int+ ghl n pps =+ if any (isJust . head) pps+ then n+ else ghl (n+1) $ map tail pps++make_chunk :: Int -> [Maybe B.ByteString] -> [B.ByteString]+make_chunk n mls = pull_chunk $ drop (n-1) mls+ where pull_chunk (Just l:mls') = l : pull_chunk mls'+ pull_chunk (Nothing:_) = []+ pull_chunk [] = bug "should this be [] in pull_chunk?"++mangle_unravelled_hunks :: [Sealed (FL Prim C(x))] -> Sealed (Prim C(x))+--mangle_unravelled_hunks [[h1],[h2]] = Deal with simple cases handily?+mangle_unravelled_hunks pss =+ if null nchs then bug "mangle_unravelled_hunks"+ else Sealed (FP filename (Hunk l old new))+ where oldf = get_old (repeat Nothing) pss+ newfs = map (get_hunks_new oldf) pss+ l = get_hunkline $ oldf : newfs+ nchs = sort $ map (make_chunk l) newfs+ filename = get_a_filename pss+ old = make_chunk l oldf+ new = [top] ++ concat (intersperse [middle] nchs) ++ [bottom]+ top = BC.pack $ "v v v v v v v" ++ eol_c+ middle = BC.pack $ "*************" ++ eol_c+ bottom = BC.pack $ "^ ^ ^ ^ ^ ^ ^" ++ eol_c+ eol_c = if any (\ps -> not (B.null ps) && BC.last ps == '\r') old+ then "\r"+ else ""++instance Effect Patch where+ effect p@(Merger _ _ _ _) = sort_coalesceFL $ effect $ merger_undo p+ effect p@(Regrem _ _ _ _) = invert $ effect $ invert p+ effect (ComP ps) = concatFL $ mapFL_FL effect ps+ effect (PP p) = effect p+ isHunk p = do PP p' <- return p+ isHunk p'++modernize_patch :: Patch C(x y) -> Patch C(x y)+modernize_patch p@(Merger _ _ _ _) = fromPrims $ effect p+modernize_patch p@(Regrem _ _ _ _) = fromPrims $ effect p+modernize_patch (ComP ps) = ComP $ filtermv $ mapFL_FL modernize_patch ps+ where filtermv :: FL Patch C(x y) -> FL Patch C(x y)+#ifndef GADT_WITNESSES+ filtermv (PP (Move _ b):>:xs) | hasadd xs = filtermv xs+ where hasadd (PP (FP b' AddFile):>:_) | b' == b = True+ hasadd (PP (DP b' AddDir):>:_) | b' == b = True+ hasadd (PP (FP b' RmFile):>:_) | b' == b = False+ hasadd (PP (DP b' RmDir):>:_) | b' == b = False+ hasadd (_:>:z) = hasadd z+ hasadd NilFL = False+#endif+ filtermv (x:>:xs) = x :>: filtermv xs+ filtermv NilFL = NilFL++modernize_patch (PP p) = fromPrims $ modernizePrim p++instance FromPrims Patch where+ fromPrims (p :>: NilFL) = PP p+ fromPrims ps = join_patchesFL $ mapFL_FL PP ps+ joinPatches = join_patchesFL++#ifndef GADT_WITNESSES+new_ur :: Patch -> RL Patch -> [RL Patch]+new_ur p (Merger _ _ p1 p2 :<: ps) =+ case filter ((`unsafeCompare` p1) . headRL) $ head_permutationsRL ps of+ ((_:<:ps'):_) -> new_ur p (p1:<:ps') ++ new_ur p (p2:<:ps')+ _ -> bugDoc $ text "in function new_ur"+ $$ text "Original patch:"+ $$ showPatch_ p+ $$ text "Unwound:"+ $$ vcat (mapRL showPatch_ $ unwind p)++new_ur op ps =+ case filter (is_merger.headRL) $ head_permutationsRL ps of+ [] -> [ps]+ (ps':_) -> new_ur op ps'++headRL :: RL a -> a+headRL (x:<:_) = x+headRL _ = bug "bad headRL"+#endif++instance Invert p => Invert (Named p) where+ invert (NamedP n d p) = NamedP (invert_name n) (map invert_name d) (invert p)+ identity = NamedP idpatchinfo [] identity++instance Invert Patch where+ invert (Merger undo unwindings p1 p2)+ = Regrem undo unwindings p1 p2+ invert (Regrem undo unwindings p1 p2)+ = Merger undo unwindings p1 p2+ invert (PP p) = PP (invert p)+ invert (ComP ps) = ComP $ invert ps+ identity = ComP NilFL++instance MyEq Patch where+ unsafeCompare = eq_patches+instance MyEq p => MyEq (Named p) where+ unsafeCompare (NamedP n1 d1 p1) (NamedP n2 d2 p2) =+ n1 == n2 && d1 == d2 && unsafeCompare p1 p2++eq_patches :: Patch C(x y) -> Patch C(w z) -> Bool+eq_patches (PP p1) (PP p2) = unsafeCompare p1 p2+eq_patches (ComP ps1) (ComP ps2)+ = eq_FL eq_patches ps1 ps2+eq_patches (ComP NilFL) (PP Identity) = True+eq_patches (PP Identity) (ComP NilFL) = True+eq_patches (Merger _ _ p1a p1b) (Merger _ _ p2a p2b)+ = eq_patches p1a p2a &&+ eq_patches p1b p2b+eq_patches (Regrem _ _ p1a p1b) (Regrem _ _ p2a p2b)+ = eq_patches p1a p2a &&+ eq_patches p1b p2b+eq_patches _ _ = False++eq_FL :: (FORALL(b c d e) a C(b c) -> a C(d e) -> Bool)+ -> FL a C(x y) -> FL a C(w z) -> Bool+eq_FL _ NilFL NilFL = True+eq_FL f (x:>:xs) (y:>:ys) = f x y && eq_FL f xs ys+eq_FL _ _ _ = False++\end{code}
@@ -0,0 +1,173 @@+% Copyright (C) 2002-2003 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.+++\section{Patch relationships}++\begin{code}+{-# OPTIONS_GHC -cpp -fglasgow-exts #-}+{-# LANGUAGE CPP #-}+-- , GADTs, PatternGuards #-}++#include "gadts.h"++module Darcs.Patch.Core+ ( Patch(..), Named(..),+ join_patchesFL, concatFL, flattenFL,+ nullP, is_null_patch, infopatch,+ n_fn,+ adddeps, namepatch, anonymous,+ merger_undo, is_merger,+ getdeps,+ patch2patchinfo, patchname, patchcontents,+ )+ where++import Prelude hiding ( pi )+import Darcs.Patch.Info ( PatchInfo, patchinfo, make_filename )+import Darcs.Patch.Patchy ( Patchy )+import Darcs.Ordered+import Darcs.Patch.Prim ( Prim(..), FromPrim(..), Effect(effect, effectRL), n_fn )+#include "impossible.h"++data Patch C(x y) where+ PP :: Prim C(x y) -> Patch C(x y)+ ComP :: FL Patch C(x y) -> Patch C(x y)+ Merger :: Patch C(x y)+ -> RL Patch C(x b)+ -> Patch C(c b)+ -> Patch C(c d)+ -> Patch C(x y)+ Regrem :: Patch C(x y)+ -> RL Patch C(x b)+ -> Patch C(c b)+ -> Patch C(c a)+ -> Patch C(y x)++instance FromPrim Patch where+ fromPrim = PP++data Named p C(x y) where+ NamedP :: !PatchInfo -> ![PatchInfo] -> !(p C(x y)) -> Named p C(x y)++instance Effect p => Effect (Named p) where+ effect (NamedP _ _ p) = effect p+ effectRL (NamedP _ _ p) = effectRL p++is_null_patch :: Patch C(x y) -> Bool+is_null_patch (ComP ps) = and $ mapFL is_null_patch ps+is_null_patch _ = False++nullP :: Patch C(x y) -> EqCheck C(x y)+nullP (ComP NilFL) = IsEq+nullP (ComP (x:>:xs)) | IsEq <- nullP x = nullP (ComP xs)+nullP _ = NotEq++is_merger :: Patch C(a b) -> Bool+is_merger (Merger _ _ _ _) = True+is_merger (Regrem _ _ _ _) = True+is_merger _ = False++merger_undo :: Patch C(x y) -> Patch C(x y)+merger_undo (Merger undo _ _ _) = undo+merger_undo _ = impossible+\end{code}++%Another nice thing to be able to do with composite patches is to `flatten'+%them, that is, turn them into a simple list of patches (appropriately+%ordered, of course), with all nested compositeness unnested.++\begin{code}+{- INLINE flattenFL -}+flattenFL :: Patch C(x y) -> FL Patch C(x y)+flattenFL (ComP ps) = concatFL (mapFL_FL flattenFL ps)+flattenFL (PP Identity) = NilFL+flattenFL p = p :>: NilFL++join_patchesFL :: FL Patch C(x y) -> Patch C(x y)+join_patchesFL ps = ComP $! ps++infopatch :: Patchy p => PatchInfo -> p C(x y) -> Named p C(x y)+adddeps :: Named p C(x y) -> [PatchInfo] -> Named p C(x y)+getdeps :: Named p C(x y) -> [PatchInfo]++namepatch :: Patchy p => String -> String -> String -> [String] -> p C(x y) -> IO (Named p C(x y))+namepatch date name author desc p+ | '\n' `elem` name = error "Patch names cannot contain newlines."+ | otherwise = do pinf <- patchinfo date name author desc+ return $ NamedP pinf [] p++anonymous :: Patchy p => p C(x y) -> IO (Named p C(x y))+anonymous p = namepatch "today" "anonymous" "unknown" ["anonymous"] p++infopatch pi p = NamedP pi [] p+adddeps (NamedP pi _ p) ds = NamedP pi ds p+getdeps (NamedP _ ds _) = ds++patch2patchinfo :: Named p C(x y) -> PatchInfo+patch2patchinfo (NamedP i _ _) = i++patchname :: Named p C(x y) -> String+patchname (NamedP i _ _) = make_filename i++patchcontents :: Named p C(x y) -> p C(x y)+patchcontents (NamedP _ _ p) = p+\end{code}++The simplest relationship between two patches is that of ``sequential''+patches, which means that the context of the second patch (the one on the+left) consists of the first patch (on the right) plus the context of the+first patch. The composition of two patches (which is also a patch) refers+to the patch which is formed by first applying one and then the other. The+composition of two patches, $P_1$ and $P_2$ is represented as $P_2P_1$,+where $P_1$ is to be applied first, then $P_2$\footnote{This notation is+inspired by the notation of matrix multiplication or the application of+operators upon a Hilbert space. In the algebra of patches, there is+multiplication (i.e.\ composition), which is associative but not+commutative, but no addition or subtraction.}++There is one other very useful relationship that two patches can have,+which is to be parallel patches, which means that the two patches have an+identical context (i.e.\ their representation applies to identical trees).+This is represented by $P_1\parallel P_2$. Of course, two patches may also+have no simple relationship to one another. In that case, if you want to+do something with them, you'll have to manipulate them with respect to+other patches until they are either in sequence or in parallel.++The most fundamental and simple property of patches is that they must be+invertible. The inverse of a patch is described by: $P^{ -1}$. In the+darcs implementation, the inverse is required to be computable from+knowledge of the patch only, without knowledge of its context, but that+(although convenient) is not required by the theory of patches.+\begin{dfn}+The inverse of patch $P$ is $P^{ -1}$, which is the ``simplest'' patch for+which the composition \( P^{ -1} P \) makes no changes to the tree.+\end{dfn}+Using this definition, it is trivial to prove the following theorem+relating to the inverse of a composition of two patches.+\begin{thm} The inverse of the composition of two patches is+\[ (P_2 P_1)^{ -1} = P_1^{ -1} P_2^{ -1}. \]+\end{thm}+Moreover, it is possible to show that the right inverse of a patch is equal+to its left inverse. In this respect, patches continue to be analogous to+square matrices, and indeed the proofs relating to these properties of the+inverse are entirely analogous to the proofs in the case of matrix+multiplication. The compositions proofs can also readily be extended to+the composition of more than two patches.+\begin{code}++\end{code}
@@ -0,0 +1,498 @@+-- Copyright (C) 2003-2004 David Roundy+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.++{-# OPTIONS_GHC -cpp -fglasgow-exts #-}+{-# LANGUAGE CPP #-}+-- , ScopedTypeVariables, TypeOperators #-}++#include "gadts.h"++module Darcs.Patch.Depends ( get_common_and_uncommon, get_tags_right,+ get_common_and_uncommon_or_missing,+ optimize_patchset, deep_optimize_patchset,+ slightly_optimize_patchset,+ get_patches_beyond_tag, get_patches_in_tag,+ patchset_union, patchset_intersection,+ commute_to_end,+ ) where+import Data.List ( delete, intersect )+import Control.Monad ( liftM2 )+import Control.Monad.Error ( Error(..) )++import Darcs.Patch ( RepoPatch, Named, getdeps, commutex,+ commuteFL,+ patch2patchinfo, merge )+import Darcs.Ordered ( (:\/:)(..), (:<)(..), (:/\:)(..), (:>)(..),+ RL(..), FL(..),+ (+<+),+ reverseFL, mapFL_FL, mapFL, concatReverseFL,+ lengthRL, concatRL, reverseRL, mapRL,+ unsafeCoerceP, EqCheck(..) )+import Darcs.Patch.Permutations ( partitionRL )+import Darcs.Patch.Info ( PatchInfo, human_friendly, is_tag )+import Darcs.Patch.Set ( PatchSet, SealedPatchSet )+import Darcs.Patch.Patchy ( sloppyIdentity )+import Darcs.Hopefully ( PatchInfoAnd, piap, info, n2pia,+ hopefully, conscientiously, hopefullyM )+import Darcs.ProgressPatches ( progressRL )+import Darcs.Sealed (Sealed(..), FlippedSeal(..), Sealed2(..)+ , flipSeal, seal, unseal )+import Printer ( errorDoc, renderString, ($$), text )+#include "impossible.h"++get_common_and_uncommon :: RepoPatch p => (PatchSet p C(x),PatchSet p C(y)) ->+ ([PatchInfo],(RL (RL (PatchInfoAnd p)) :\/: RL (RL (PatchInfoAnd p))) C(x y))+get_common_and_uncommon_or_missing :: RepoPatch p => (PatchSet p C(x),PatchSet p C(y)) ->+ Either PatchInfo ([PatchInfo],(RL (RL (PatchInfoAnd p)) :\/: RL (RL (PatchInfoAnd p))) C(x y))++get_common_and_uncommon = + either missingPatchError id . get_common_and_uncommon_err++get_common_and_uncommon_or_missing = + either (\(MissingPatch x _) -> Left x) Right . get_common_and_uncommon_err++get_common_and_uncommon_err :: RepoPatch p => (PatchSet p C(x),PatchSet p C(y)) ->+ Either MissingPatch ([PatchInfo],(RL (RL (PatchInfoAnd p)) :\/: RL (RL (PatchInfoAnd p))) C(x y))+get_common_and_uncommon_err (ps1,ps2) = gcau (optimize_patchset ps1) ps2++{-|+with_partial_intersection takes two 'PatchSet's and splits them into a /common/+intersection portion and two sets of patches. The intersection, however,+is only lazily determined, so there is no guarantee that all intersecting+patches will be included in the intersection 'PatchSet'. This is a pretty+efficient function, because it makes use of the already-broken-up nature of+'PatchSet's.++'PatchSet's have the property that if+@+(info $ last $ head a) == (info $ last $ head b)+@+then @(tail a)@ and @(tail b)@ are identical repositories, and we want to take+advantage of this if possible, to avoid reading too many inventories. In+the case of --partial repositories or patch bundles, it is crucial that we+don't need to read the whole history, since it isn't available.++TODO:++The length equalising isn't necessarily right. We probably also be+thinking about not going past the end of a partial repository, or favour+local repository stuff over remote repository stuff.++Also, when comparing l1 to l2, we should really be comparing the+newly discovered one to /all/ the lasts in the other patch set+that we've got so far.+-}+with_partial_intersection :: forall a p C(x y). RepoPatch p => PatchSet p C(x) -> PatchSet p C(y)+ -> (FORALL(z) PatchSet p C(z) -> RL (PatchInfoAnd p) C(z x)+ -> RL (PatchInfoAnd p) C(z y) -> a)+ -> a+with_partial_intersection NilRL ps2 j = j (NilRL:<:NilRL) NilRL (concatRL ps2)+with_partial_intersection ps1 NilRL j = j (NilRL:<:NilRL) (concatRL ps1) NilRL+with_partial_intersection (NilRL:<:ps1) ps2 j =+ with_partial_intersection ps1 ps2 j+with_partial_intersection ps1 (NilRL:<:ps2) j =+ with_partial_intersection ps1 ps2 j+-- NOTE: symmetry is broken here, so we want the PatchSet with more history+-- first!+with_partial_intersection ((pi1:<:NilRL):<:common) ((pi2:<:NilRL):<:_) j+-- NOTE: Since the patchsets have the same starting but different ending+-- we can coerce them. The type system is not aware of our invariant on tags,+-- but both pi1 and pi2 should be tags, thus we check they are both identity+-- patches.+ | info pi1 == info pi2+ , IsEq <- sloppyIdentity pi1+ , IsEq <- sloppyIdentity pi2 = j common NilRL (unsafeCoerceP NilRL)+with_partial_intersection (orig_ps1:<:orig_ps1s) (orig_ps2:<:orig_ps2s) j+ = f (lengthRL orig_ps1) (last $ mapRL info orig_ps1) (orig_ps1:>:NilFL) orig_ps1s+ (lengthRL orig_ps2) (last $ mapRL info orig_ps2) (orig_ps2:>:NilFL) orig_ps2s+ where {- Invariants: nx = length $ concatReverseFL psx+ lx = last $ concatReverseFL psx -}+ f :: Int -> PatchInfo -> FL (RL (PatchInfoAnd p)) C(r x) -> PatchSet p C(r)+ -> Int -> PatchInfo -> FL (RL (PatchInfoAnd p)) C(u y) -> PatchSet p C(u)+ -> a+ f _n1 l1 ps1 ps1s _n2 l2 ps2 _ps2s+ | l1 == l2 = j ps1s (unsafeCoerceP (concatReverseFL ps1)) (unsafeCoerceP (concatReverseFL ps2))+ f n1 l1 ps1 ps1s n2 l2 ps2 ps2s+ = case compare n1 n2 of+ GT -> case dropWhileNilRL ps2s of+ ps2':<:ps2s' ->+ f n1 l1 ps1 ps1s+ (n2 + lengthRL ps2') (last $ mapRL info ps2') (ps2':>:ps2) ps2s'+ NilRL -> -- We keep going round f so the l1 == l2 case+ -- has a chance to kick in+ case dropWhileNilRL ps1s of+ ps1':<:ps1s' ->+ f (n1 + lengthRL ps1') (last $ mapRL info ps1')+ (ps1':>:ps1) ps1s'+ n2 l2 ps2 ps2s+ NilRL -> j (NilRL:<:NilRL) (concatReverseFL ps1) (concatReverseFL ps2)+ _ -> case dropWhileNilRL ps1s of+ ps1':<:ps1s' ->+ f (n1 + lengthRL ps1') (last $ mapRL info ps1') (ps1':>:ps1) ps1s'+ n2 l2 ps2 ps2s+ NilRL -> -- We keep going round f so the l1 == l2 case+ -- has a chance to kick in+ case dropWhileNilRL ps2s of+ ps2':<:ps2s' ->+ f n1 l1 ps1 NilRL+ (n2 + lengthRL ps2') (last $ mapRL info ps2')+ (ps2':>:ps2) ps2s'+ NilRL -> j (NilRL:<:NilRL) (concatReverseFL ps1) (concatReverseFL ps2)++{-|+'gcau' determines a list of /common/ patches and patches unique to each of+the two 'PatchSet's. The list of /common/ patches only needs to include all+patches that are not interspersed with the /unique/ patches, but including+more patches in the list of /common/ patches doesn't really hurt, except+for efficiency considerations. Mostly, we want to access as few elements+as possible of the 'PatchSet' list, since those can be expensive (or+unavailable). In other words, the /common/ patches need not be minimal,+whereas the 'PatchSet's should be minimal for performance reasons.++'PatchSet's have the property that if+@+(info $ last $ head a) == (info $ last $ head b)+@+then @(tail a)@ and @(tail b)@ are identical repositories, and we want to take+advantage of this if possible, to avoid reading too many inventories. In+the case of --partial repositories or patch bundles, it is crucial that we+don't need to read the whole history, since it isn't available.++TODO:++The length equalising isn't necessarily right. We probably also be+thinking about not going past the end of a partial repository, or favour+local repository stuff over remote repo stuff.++Also, when comparing l1 to l2, we should really be comparing the+newly discovered one to /all/ the lasts in the other patch set+that we've got so far.+-}++gcau :: forall p C(x y). RepoPatch p => PatchSet p C(x) -> PatchSet p C(y)+ -> Either MissingPatch ([PatchInfo],(RL (RL (PatchInfoAnd p)) :\/: RL (RL (PatchInfoAnd p))) C(x y))+gcau NilRL ps2 = return ([], NilRL:<:NilRL :\/: concatRL ps2 :<: NilRL)+gcau ps1 NilRL = return ([], concatRL ps1 :<: NilRL :\/: NilRL:<:NilRL)+gcau (NilRL:<:ps1) ps2 = gcau ps1 ps2+gcau ps1 (NilRL:<:ps2) = gcau ps1 ps2+gcau ((pi1:<:NilRL):<:_) ((pi2:<:NilRL):<:_)+ | info pi1 == info pi2+ , IsEq <- sloppyIdentity pi1+ , IsEq <- sloppyIdentity pi2 = return ([info pi1], NilRL:<:NilRL :\/: unsafeCoerceP (NilRL:<:NilRL))+gcau (orig_ps1:<:orig_ps1s) (orig_ps2:<:orig_ps2s)+ = f (lengthRL orig_ps1) (unseal info $ lastRL orig_ps1) (orig_ps1:>:NilFL) orig_ps1s+ (lengthRL orig_ps2) (unseal info $ lastRL orig_ps2) (orig_ps2:>:NilFL) orig_ps2s+ where {- Invariants: nx = lengthRL $ concatReverseFL psx+ lx = last $ concatReverseFL psx -}+ f :: Int -> PatchInfo -> FL (RL (PatchInfoAnd p)) C(r x) -> PatchSet p C(r)+ -> Int -> PatchInfo -> FL (RL (PatchInfoAnd p)) C(u y) -> PatchSet p C(u)+ -> Either MissingPatch ([PatchInfo],(RL (RL (PatchInfoAnd p)) :\/: RL (RL (PatchInfoAnd p))) C(x y))+ f _n1 l1 ps1 _ps1s _n2 l2 ps2 _ps2s+ | l1 == l2 = gcau_simple (unsafeCoerceP (concatReverseFL ps1)) (unsafeCoerceP (concatReverseFL ps2))+ f n1 l1 ps1 ps1s n2 l2 ps2 ps2s+ = case n1 `compare` n2 of+ GT -> case dropWhileNilRL ps2s of+ ps2':<:ps2s' ->+ f n1 l1 ps1 ps1s+ (n2 + lengthRL ps2') (unseal info $ lastRL ps2') (ps2':>:ps2) ps2s'+ NilRL -> -- We keep going round f so the l1 == l2 case+ -- has a chance to kick in+ case dropWhileNilRL ps1s of+ ps1':<:ps1s' ->+ f (n1 + lengthRL ps1') (unseal info $ lastRL ps1')+ (ps1':>:ps1) ps1s'+ n2 l2 ps2 ps2s+ NilRL -> gcau_simple (concatReverseFL ps1) (concatReverseFL ps2)+ _ -> case dropWhileNilRL ps1s of+ ps1':<:ps1s' ->+ f (n1 + lengthRL ps1') (unseal info $ lastRL ps1') (ps1':>:ps1) ps1s'+ n2 l2 ps2 ps2s+ NilRL -> -- We keep going round f so the l1 == l2 case+ -- has a chance to kick in+ case dropWhileNilRL ps2s of+ ps2':<:ps2s' ->+ f n1 l1 ps1 NilRL+ (n2 + lengthRL ps2') (unseal info $ lastRL ps2')+ (ps2':>:ps2) ps2s'+ NilRL -> gcau_simple (concatReverseFL ps1) (concatReverseFL ps2)++lastRL :: RL a C(x y) -> Sealed (a C(x))+lastRL (a:<:NilRL) = seal a+lastRL (_:<:as) = lastRL as+lastRL NilRL = bug "lastRL on empty list"++dropWhileNilRL :: PatchSet p C(x) -> PatchSet p C(x)+dropWhileNilRL (NilRL:<:xs) = dropWhileNilRL xs+dropWhileNilRL xs = xs++-- | Filters the common elements from @ps1@ and @ps2@ and returns the simplified sequences.+gcau_simple :: RepoPatch p => RL (PatchInfoAnd p) C(x y) -- ^ @ps1@+ -> RL (PatchInfoAnd p) C(u v) -- ^ @ps2@+ -> Either MissingPatch ([PatchInfo],(RL (RL (PatchInfoAnd p)) :\/: RL (RL (PatchInfoAnd p))) C(y v))+gcau_simple ps1 ps2 = do+ FlippedSeal ex1 <- get_extra common ps1+ FlippedSeal ex2 <- get_extra common ps2+ let ps1' = filter (`elem` common) $ ps1_info+ return (ps1', (unsafeCoerceP ex1 :<: NilRL) :\/: ex2 :<: NilRL)+ where common = ps1_info `intersect` mapRL info ps2+ ps1_info = mapRL info ps1++data MissingPatch = MissingPatch !PatchInfo !String++instance Error MissingPatch where+ -- we don't really need those methods+ noMsg = bug "MissingPatch doesn't define noMsg."++-- | Returns a sub-sequence from @patches@, where all the elements of @common@ have+-- been removed by commuting them out.+get_extra :: RepoPatch p => [PatchInfo] -- ^ @common@+ -> RL (PatchInfoAnd p) C(u x) -- ^ @patches@+ -> Either MissingPatch (FlippedSeal (RL (PatchInfoAnd p)) C(y))+get_extra = get_extra_aux (return $ unsafeCoerceP NilFL)+ where+ get_extra_aux :: RepoPatch p => Either MissingPatch (FL (Named p) C(x y))+ -> [PatchInfo]+ -> RL (PatchInfoAnd p) C(u x)+ -> Either MissingPatch (FlippedSeal (RL (PatchInfoAnd p)) C(y))+ get_extra_aux _ _ NilRL = return (flipSeal NilRL)+ get_extra_aux skipped common (hp:<:pps) =+ if info hp `elem` common && is_tag (info hp)+ then case getdeps `fmap` hopefullyM hp of+ Just ds -> get_extra_aux (liftM2 (:>:) ep skipped) (ds++delete (info hp) common) pps+ Nothing -> get_extra_aux (liftM2 (:>:) ep skipped) (delete (info hp) common) pps+ else if info hp `elem` common+ then get_extra_aux (liftM2 (:>:) ep skipped) (delete (info hp) common) pps+ else do+ p <- ep+ skpd <- skipped+ case commuteFL (p :> skpd) of+ Right (skipped_patch' :> p') -> do+ FlippedSeal x <- get_extra_aux (return skipped_patch') common pps+ return $ flipSeal (info hp `piap` p' :<: x)+ -- Failure to commute indicates a bug because it means+ -- that a patch was interspersed between the common+ -- patches. This should only happen if that patch was+ -- commuted there. This uses 2 properties:+ -- 1) commute is its own inverse+ -- 2) if patches commute in one adjacent context then+ -- they commute in any context where they are+ -- adjacent+ Left (Sealed2 hpc) -> errorDoc $ text "bug in get_extra commuting patches:"+ $$ text "First patch is:"+ $$ human_friendly (info hp)+ $$ text "Second patch is:"+ $$ human_friendly (info $ n2pia hpc)+ where ep = case hopefullyM hp of+ Right p' -> return p'+ Left e -> Left (MissingPatch (info hp) e)++missingPatchError :: MissingPatch -> a+missingPatchError (MissingPatch pinfo e) =+ errorDoc+ ( text "failed to read patch in get_extra:"+ $$ human_friendly pinfo $$ text e+ $$ text "Perhaps this is a 'partial' repository?" )++get_extra_old :: RepoPatch p => [PatchInfo]+ -> RL (PatchInfoAnd p) C(u x)+ -> FlippedSeal (RL (PatchInfoAnd p)) C(y)+get_extra_old common pps =+ either missingPatchError id (get_extra common pps)++get_patches_beyond_tag :: RepoPatch p => PatchInfo -> PatchSet p C(x) -> FlippedSeal (RL (RL (PatchInfoAnd p))) C(x)+get_patches_beyond_tag t ((hp:<:NilRL):<:_) | info hp == t = flipSeal $ NilRL :<: NilRL+get_patches_beyond_tag t patchset@((hp:<:ps):<:pps) =+ if info hp == t+ then if get_tags_right patchset == [info hp]+ then flipSeal $ NilRL :<: NilRL -- special case to avoid looking at redundant patches+ else case get_extra_old [t] (concatRL patchset) of+ FlippedSeal x -> flipSeal $ x :<: NilRL+ else hp `prepend` get_patches_beyond_tag t (ps:<:pps)+ where+ prepend :: (PatchInfoAnd p) C(x y) -> FlippedSeal (RL (RL (PatchInfoAnd p))) C(x) -> FlippedSeal (RL (RL (PatchInfoAnd p))) C(y)+ prepend pp (FlippedSeal NilRL) = flipSeal $ (pp:<:NilRL) :<: NilRL+ prepend pp (FlippedSeal (p:<:ps')) = flipSeal $ (pp:<:p) :<: ps'+get_patches_beyond_tag t (NilRL:<:pps) = get_patches_beyond_tag t pps+get_patches_beyond_tag t NilRL = bug $ "tag\n" +++ renderString (human_friendly t) +++ "\nis not in the patchset in get_patches_beyond_tag."++-- | @get_patches_in_tag t ps@ returns a 'SealedPatchSet' of all+-- patches in @ps@ which are contained in @t@.+get_patches_in_tag :: RepoPatch p => PatchInfo -> PatchSet p C(x) -> SealedPatchSet p+get_patches_in_tag t pps@((hp:<:NilRL):<:xs)+ | info hp == t = seal pps+ | otherwise = get_patches_in_tag t xs++get_patches_in_tag t ((hp:<:ps):<:xs)+ | info hp /= t = get_patches_in_tag t (ps:<:xs)++get_patches_in_tag t ((pa:<:ps):<:xs) = gpit thepis (pa:>:NilFL) (ps:<:xs)+ where thepis = getdeps $ conscientiously+ (\e -> text "Couldn't read tag"+ $$ human_friendly t+ $$ text ""+ $$ e) pa+ gpit :: RepoPatch p => [PatchInfo] -> (FL (PatchInfoAnd p)) C(x y) -> PatchSet p C(x) -> SealedPatchSet p+ gpit _ sofar NilRL = seal $ reverseFL sofar :<: NilRL+ gpit deps sofar ((hp:<:NilRL):<:xs')+ | info hp `elem` deps+ , IsEq <- sloppyIdentity hp = seal $ (reverseFL $ hp :>: sofar) :<: xs'+ | IsEq <- sloppyIdentity hp = gpit deps sofar xs'+ gpit deps sofar (NilRL:<:xs') = gpit deps sofar xs'+ gpit deps sofar ((hp:<:ps'):<:xs')+ | info hp `elem` deps+ = let odeps = filter (/=info hp) deps+ alldeps = if is_tag $ info hp+ then odeps ++ getdeps (hopefully hp)+ else odeps+ in gpit alldeps (hp:>:sofar) (ps':<:xs')+ | otherwise+ = gpit deps (commute_by sofar $ hopefully hp) (ps':<:xs')+ commute_by :: RepoPatch p => FL (PatchInfoAnd p) C(x y) -> (Named p) C(w x)+ -> FL (PatchInfoAnd p) C(w z)+ commute_by NilFL _ = unsafeCoerceP NilFL+ commute_by (hpa:>:xs') p =+ case commutex (hopefully hpa :< p) of+ Nothing -> bug "Failure commuting patches in commute_by called by gpit!"+ Just (p':<a') -> (info hpa `piap` a') :>: commute_by xs' p'++get_patches_in_tag t _ = errorDoc $ text "Couldn't read tag"+ $$ human_friendly t++get_tags_right :: RL (RL (PatchInfoAnd p)) C(x y) -> [PatchInfo]+get_tags_right NilRL = []+get_tags_right (ps:<:_) = get_tags_r (mapRL info_and_deps ps)+ where+ get_tags_r :: [(PatchInfo, Maybe [PatchInfo])] -> [PatchInfo]+ get_tags_r [] = []+ get_tags_r (hp:pps) = case snd hp of+ Just ds -> fst hp : get_tags_r (drop_tags_r ds pps)+ Nothing -> fst hp : get_tags_r pps++ drop_tags_r :: [PatchInfo]+ -> [(PatchInfo, Maybe [PatchInfo])] -> [(PatchInfo, Maybe [PatchInfo])]+ drop_tags_r [] pps = pps+ drop_tags_r _ [] = []+ drop_tags_r ds (hp:pps)+ | fst hp `elem` ds = case snd hp of+ Just ds' -> drop_tags_r (ds'++delete (fst hp) ds) pps+ Nothing -> drop_tags_r (delete (fst hp) ds) pps+ | otherwise = hp : drop_tags_r ds pps+ + info_and_deps :: PatchInfoAnd p C(x y) -> (PatchInfo, Maybe [PatchInfo])+ info_and_deps p + | is_tag (info p) = (info p, getdeps `fmap` hopefullyM p)+ | otherwise = (info p, Nothing)++deep_optimize_patchset :: PatchSet p C(x) -> PatchSet p C(x)+deep_optimize_patchset pss = optimize_patchset (concatRL pss :<: NilRL)++optimize_patchset :: PatchSet p C(x) -> PatchSet p C(x)+optimize_patchset NilRL = NilRL+optimize_patchset (ps:<:pss) = opsp ps +<+ pss+ where + opsp :: RL (PatchInfoAnd p) C(x y) -> RL (RL (PatchInfoAnd p)) C(x y)+ opsp NilRL = NilRL+ opsp (hp:<:pps)+ | is_tag (info hp) && get_tags_right ((hp:<:pps):<:NilRL) == [info hp]+ = (hp:<:NilRL) :<: opsp pps+ | otherwise = hp -:- opsp pps++(-:-) :: (PatchInfoAnd p) C(x y) -> RL (RL (PatchInfoAnd p)) C(a x) -> RL (RL (PatchInfoAnd p)) C(a y)+pp -:- NilRL = (pp:<:NilRL) :<: NilRL+pp -:- (p:<:ps) = ((pp:<:p) :<: ps)++slightly_optimize_patchset :: PatchSet p C(x) -> PatchSet p C(x)+slightly_optimize_patchset NilRL = NilRL+slightly_optimize_patchset (ps:<:pss) = sops (progressRL "Optimizing inventory" ps) +<+ pss+ where sops :: RL (PatchInfoAnd p) C(x y) -> RL (RL (PatchInfoAnd p)) C(x y)+ sops NilRL = NilRL+ sops (pinfomp :<: NilRL) = (pinfomp :<: NilRL) :<: NilRL+ sops (hp:<:pps) | is_tag (info hp) = if get_tags_right ((hp:<:pps):<:NilRL) == [info hp]+ then (hp:<:NilRL) :<: (pps:<: NilRL)+ else hp -:- sops (progressRL "Optimizing inventory" pps)+ | otherwise = hp -:- sops pps++commute_to_end :: forall p C(x y). RepoPatch p => FL (Named p) C(x y) -> PatchSet p C(y)+ -> (FL (Named p) :< RL (RL (PatchInfoAnd p))) C(() x)+commute_to_end select from = ctt (mapFL patch2patchinfo select) from NilFL+ where+-- In order to preserve the structure of the original PatchSet, we commute+-- the patches we are going to throw away past the patches we plan to keep.+-- This puts them at the end of the PatchSet where it is safe to discard them.+-- We return all the patches in the PatchSet which have been commuted.+ ctt :: [PatchInfo] -> PatchSet p C(v) -> FL (Named p) C(v u)+ -> (FL (Named p) :< RL (RL (PatchInfoAnd p))) C(() x)+ -- This unsafeCoerceP should be fine, because if we run out of+ -- patches in the selection the ending context of the second param+ -- should be x (because we have commute all of the selected sequence,+ -- with context C(x y), past the elements of the second parameter.+ -- Unfortunately this is hard to express in the type system while+ -- using an accumulator to build up the return value.+ ctt [] ps acc = (unsafeCoerceP acc) :< ps+ ctt sel (NilRL:<:ps) acc = ctt sel ps acc+ ctt sel ((hp:<:hps):<:ps) acc+ | info hp `elem` sel+ = case commuteFL (hopefully hp :> acc) of+ Left _ -> bug "patches to commute_to_end does not commutex (1)"+ Right (acc' :> _) -> ctt (delete (info hp) sel) (hps:<:ps) acc'+ | otherwise+ = ctt sel (hps:<:ps) (hopefully hp:>:acc)+ ctt _ _ _ = bug "patches to commute_to_end does not commutex (2)"++patchset_intersection :: RepoPatch p => [SealedPatchSet p] -> SealedPatchSet p+patchset_intersection [] = seal (NilRL :<: NilRL)+patchset_intersection [x] = x+patchset_intersection (Sealed y:ys) = + case patchset_intersection ys of+ Sealed ys' -> with_partial_intersection y ys' $+ \common a b -> + case mapRL info a `intersect` mapRL info b of+ morecommon -> + case partitionRL (\e -> info e `notElem` morecommon) a of+ commonps :> _ -> seal $ commonps :<: common++patchset_union :: forall p. RepoPatch p => [SealedPatchSet p] -> SealedPatchSet p+patchset_union [] = seal (NilRL :<: NilRL)+patchset_union [x] = x+patchset_union (Sealed y:ys) = + case patchset_union ys of+ Sealed ys' -> with_partial_intersection y ys' f+ where+ f :: FORALL(z x y) PatchSet p C(z) -- ^ @common@+ -> RL (PatchInfoAnd p) C(z x) -- ^ @a@+ -> RL (PatchInfoAnd p) C(z y) -- ^ @b@+ -> SealedPatchSet p+ f common a b = g_s $ gcau_simple a b+ where+ g_s :: Either MissingPatch+ ([PatchInfo],(RL (RL (PatchInfoAnd p)) :\/: RL (RL (PatchInfoAnd p))) C(x y))+ -> SealedPatchSet p+ g_s (Left e) = missingPatchError e+ g_s (Right (_, (a' :<: NilRL) :\/: (b' :<: NilRL))) =+ case (merge_sets (a' :\/: b')) of+ Sealed a'b' -> seal $ (a'b' +<+ b) :<: common+ g_s _ = impossible++merge_sets :: RepoPatch p => (RL (PatchInfoAnd p) :\/: RL (PatchInfoAnd p)) C(x y) -> Sealed (RL (PatchInfoAnd p) C(y))+merge_sets (l :\/: r) =+ let pl = mapFL_FL hopefully $ reverseRL l+ pr = mapFL_FL hopefully $ reverseRL r+ p2pimp p = patch2patchinfo p `piap` p+ in case merge (pl:\/: pr) of+ (_:/\:pl') -> seal $ reverseFL $ mapFL_FL p2pimp pl'
@@ -0,0 +1,158 @@+-- Copyright (C) 2002-2003 David Roundy+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.++-- | FileName is an abstract type intended to facilitate the input and output of+-- unicode filenames.+module Darcs.Patch.FileName ( FileName( ),+ fp2fn, fn2fp,+ fn2ps, ps2fn,+ niceps2fn, fn2niceps,+ break_on_dir, norm_path, own_name, super_name,+ movedirfilename,+ encode_white, decode_white,+ (///),+ breakup+ ) where++import System.IO+import Data.Char ( isSpace, chr, ord )+import qualified UTF8 ( encode )+import Data.Word ( Word8( ) )+import ByteStringUtils ( unpackPSfromUTF8 )+import qualified Data.ByteString.Char8 as BC (unpack, pack)+import qualified Data.ByteString as B (ByteString, pack)++newtype FileName = FN FilePath deriving ( Eq, Ord )+encode :: [Char] -> [Word8]+encode = UTF8.encode++instance Show FileName where+ showsPrec d (FN fp) = showParen (d > app_prec) $ showString "fp2fn " . showsPrec (app_prec + 1) fp+ where app_prec = 10++{-# INLINE fp2fn #-}+fp2fn :: FilePath -> FileName+fp2fn fp = FN fp++{-# INLINE fn2fp #-}+fn2fp :: FileName -> FilePath+fn2fp (FN fp) = fp++{-# INLINE niceps2fn #-}+niceps2fn :: B.ByteString -> FileName+niceps2fn = FN . decode_white . BC.unpack++{-# INLINE fn2niceps #-}+fn2niceps :: FileName -> B.ByteString+fn2niceps (FN fp) = BC.pack $ encode_white fp++{-# INLINE fn2ps #-}+fn2ps :: FileName -> B.ByteString+fn2ps (FN fp) = B.pack $ encode $ encode_white fp++{-# INLINE ps2fn #-}+ps2fn :: B.ByteString -> FileName+ps2fn ps = FN $ decode_white $ unpackPSfromUTF8 ps++-- | 'encode_white' translates whitespace in filenames to a darcs-specific+-- format (backslash followed by numerical representation according to 'ord').+-- Note that backslashes are also escaped since they are used in the encoding.+--+-- > encode_white "hello there" == "hello\32there"+-- > encode_white "hello\there" == "hello\\there"+encode_white :: FilePath -> String+encode_white (c:cs) | isSpace c || c == '\\' =+ '\\' : (show $ ord c) ++ "\\" ++ encode_white cs+encode_white (c:cs) = c : encode_white cs+encode_white [] = []++-- | 'decode_white' interprets the Darcs-specific \"encoded\" filenames+-- produced by 'encode_white'+--+-- > decode_white "hello\32there" == "hello there"+-- > decode_white "hello\\there" == "hello\there"+-- > decode_white "hello\there" == error "malformed filename"+decode_white :: String -> FilePath+decode_white ('\\':cs) =+ case break (=='\\') cs of+ (theord, '\\':rest) ->+ chr (read theord) : decode_white rest+ _ -> error "malformed filename"+decode_white (c:cs) = c: decode_white cs+decode_white "" = ""++own_name :: FileName -> FileName+own_name (FN f) = case breakLast '/' f of Nothing -> FN f+ Just (_,f') -> FN f'+super_name :: FileName -> FileName+super_name fn = case norm_path fn of+ FN f -> case breakLast '/' f of+ Nothing -> FN "."+ Just (d,_) -> FN d+break_on_dir :: FileName -> Maybe (FileName,FileName)+break_on_dir (FN p) = case breakFirst '/' p of+ Nothing -> Nothing+ Just (d,f) | d == "." -> break_on_dir $ FN f+ | otherwise -> Just (FN d, FN f)+norm_path :: FileName -> FileName -- remove "./"+norm_path (FN p) = FN $ repath $ drop_dotdot $ breakup p++repath :: [String] -> String+repath [] = ""+repath [f] = f+repath (d:p) = d ++ "/" ++ repath p++drop_dotdot :: [String] -> [String]+drop_dotdot ("":p) = drop_dotdot p+drop_dotdot (".":p) = drop_dotdot p+drop_dotdot ("..":p) = ".." : (drop_dotdot p)+drop_dotdot (_:"..":p) = drop_dotdot p+drop_dotdot (d:p) = case drop_dotdot p of+ ("..":p') -> p'+ p' -> d : p'+drop_dotdot [] = []++-- | Split a file path at the slashes+breakup :: String -> [String]+breakup p = case break (=='/') p of+ (d,"") -> [d]+ (d,p') -> d : breakup (tail p')++breakFirst :: Char -> String -> Maybe (String,String)+breakFirst c l = bf [] l+ where bf a (r:rs) | r == c = Just (reverse a,rs)+ | otherwise = bf (r:a) rs+ bf _ [] = Nothing+breakLast :: Char -> String -> Maybe (String,String)+breakLast c l = case breakFirst c (reverse l) of+ Nothing -> Nothing+ Just (a,b) -> Just (reverse b, reverse a)++(///) :: FileName -> FileName -> FileName+(FN "")///b = norm_path b+a///b = norm_path $ fp2fn $ fn2fp a ++ "/" ++ fn2fp b++movedirfilename :: FileName -> FileName -> FileName -> FileName+movedirfilename old new name =+ if name' == old' then new+ else if length name' > length old' &&+ take (length old'+1) name' == old'++"/"+ then fp2fn ("./"++new'++drop (length old') name')+ else name+ where old' = fn2fp $ norm_path old+ new' = fn2fp $ norm_path new+ name' = fn2fp $ norm_path name
@@ -0,0 +1,331 @@+-- Copyright (C) 2002-2003 David Roundy+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.++module Darcs.Patch.Info ( PatchInfo, patchinfo, invert_name, is_inverted,+ idpatchinfo, add_junk,+ make_filename, make_alt_filename, readPatchInfo,+ just_name, just_author, repopatchinfo, RepoPatchInfo,+ human_friendly, to_xml, pi_date, set_pi_date,+ pi_name, pi_rename, pi_author, pi_tag, pi_log,+ showPatchInfo, is_tag+ ) where+import Text.Html hiding (name, text)+import System.Random ( randomRIO )+import Numeric ( showHex )+import Control.Monad ( when )++import ByteStringUtils +import qualified Data.ByteString as B (length, splitAt, null, drop+ ,isPrefixOf, tail, concat, ByteString )+import qualified Data.ByteString.Char8 as BC (index, head, unpack, pack, break)++import Printer ( renderString, Doc, packedString,+ empty, ($$), (<>), (<+>), vcat, text, blueText, prefix )+import OldDate ( readUTCDate, showIsoDateTime )+import System.Time ( CalendarTime(ctTZ), calendarTimeToString, toClockTime,+ toCalendarTime )+import System.IO.Unsafe ( unsafePerformIO )+import SHA1 ( sha1PS )+import Darcs.Utils ( promptYorn )+import Prelude hiding (pi, log)++data RepoPatchInfo = RPI String PatchInfo++repopatchinfo :: String -> PatchInfo -> RepoPatchInfo+repopatchinfo r pi = RPI r pi++data PatchInfo = PatchInfo { _pi_date :: !B.ByteString+ , _pi_name :: !B.ByteString+ , _pi_author :: !B.ByteString+ , _pi_log :: ![B.ByteString]+ , is_inverted :: !Bool+ }+ deriving (Eq,Ord)++idpatchinfo :: PatchInfo+idpatchinfo = PatchInfo myid myid myid [] False+ where myid = BC.pack "identity"++patchinfo :: String -> String -> String -> [String] -> IO PatchInfo+patchinfo date name author log =+ add_junk $ PatchInfo { _pi_date = BC.pack date+ , _pi_name = BC.pack name+ , _pi_author = BC.pack author+ , _pi_log = map BC.pack log+ , is_inverted = False }++add_junk :: PatchInfo -> IO PatchInfo+add_junk pinf =+ do x <- randomRIO (0,2^(128 ::Integer) :: Integer)+ when (_pi_log pinf /= ignore_junk (_pi_log pinf)) $+ do yorn <- promptYorn "Lines beginning with 'Ignore-this: ' will be ignored.\nProceed? "+ when (yorn == 'n') $ fail "User cancelled because of Ignore-this."+ return $ pinf { _pi_log = BC.pack (head ignored++showHex x ""):+ _pi_log pinf }++ignored :: [String] -- this is a [String] so we can change the junk header.+ignored = ["Ignore-this: "]++ignore_junk :: [B.ByteString] -> [B.ByteString]+ignore_junk = filter isnt_ignored+ where isnt_ignored x = doesnt_start_with x (map BC.pack ignored) -- TODO+ doesnt_start_with x ys = not $ any (`B.isPrefixOf` x) ys+++-- * Patch info formatting+invert_name :: PatchInfo -> PatchInfo+invert_name pi = pi { is_inverted = not (is_inverted pi) }++just_name :: PatchInfo -> String+just_name pinf = if is_inverted pinf then "UNDO: " ++ BC.unpack (_pi_name pinf)+ else BC.unpack (_pi_name pinf)++just_author :: PatchInfo -> String+just_author = BC.unpack . _pi_author++human_friendly :: PatchInfo -> Doc+human_friendly pi =+ text (friendly_d $ _pi_date pi) <> text " " <> packedString (_pi_author pi)+ $$ hfn (_pi_name pi)+ $$ vcat (map ((text " " <>) . packedString) (ignore_junk $ _pi_log pi))+ where hfn x = case pi_tag pi of+ Nothing -> inverted <+> packedString x+ Just t -> text " tagged" <+> text t+ inverted = if is_inverted pi then text " UNDO:" else text " *"++-- note the difference with just_name+pi_name :: PatchInfo -> String+pi_name = BC.unpack . _pi_name++pi_rename :: PatchInfo -> String -> PatchInfo+pi_rename x n = x { _pi_name = BC.pack n }++pi_author :: PatchInfo -> String+pi_author = BC.unpack . _pi_author++is_tag :: PatchInfo -> Bool+is_tag pinfo = take 4 (just_name pinfo) == "TAG "+++-- | Note: we ignore timezone information in the date string,+-- systematically treating a time as UTC. So if the patch+-- tells me it's 17:00 EST, we're actually treating it as+-- 17:00 UTC, in other words 11:00 EST. This is for+-- backwards compatibility to darcs prior to 2003-11, sometime+-- before 1.0. Fortunately, newer patch dates are written in+-- UTC, so this timezone truncation is harmless for them.+readPatchDate :: B.ByteString -> CalendarTime+readPatchDate = ignoreTz . readUTCDate . BC.unpack+ where ignoreTz ct = ct { ctTZ = 0 }++pi_date :: PatchInfo -> CalendarTime+pi_date = readPatchDate . _pi_date++set_pi_date :: String -> PatchInfo -> PatchInfo+set_pi_date date pi = pi { _pi_date = BC.pack date }++pi_log :: PatchInfo -> [String]+pi_log = map BC.unpack . ignore_junk . _pi_log++pi_tag :: PatchInfo -> Maybe String+pi_tag pinf =+ if l == t+ then Just $ BC.unpack r+ else Nothing+ where (l, r) = B.splitAt (B.length t) (_pi_name pinf)+ t = BC.pack "TAG "++friendly_d :: B.ByteString -> String+--friendly_d d = calendarTimeToString . readPatchDate . d+friendly_d d = unsafePerformIO $ do+ ct <- toCalendarTime $ toClockTime $ readPatchDate d+ return $ calendarTimeToString ct++to_xml :: PatchInfo -> Doc+to_xml pi =+ text "<patch"+ <+> text "author='" <> escapeXML (just_author pi) <> text "'"+ <+> text "date='" <> escapeXML (BC.unpack $ _pi_date pi) <> text "'"+ <+> text "local_date='" <> escapeXML (friendly_d $ _pi_date pi) <> text "'"+ <+> text "inverted='" <> text (show $ is_inverted pi) <> text "'"+ <+> text "hash='" <> text (make_filename pi) <> text "'>"+ $$ prefix "\t" (+ text "<name>" <> escapeXML (pi_name pi) <> text "</name>"+ $$ comments_as_xml (_pi_log pi))+ $$ text "</patch>"++comments_as_xml :: [B.ByteString] -> Doc+comments_as_xml comments+ | B.length comments' > 0 = text "<comment>"+ <> escapeXML (BC.unpack comments')+ <> text "</comment>"+ | otherwise = empty+ where comments' = unlinesPS comments++-- escapeXML is duplicated in Patch.lhs and Annotate.lhs+-- It should probably be refactored to exist in one place.+escapeXML :: String -> Doc+escapeXML = text . strReplace '\'' "'" . strReplace '"' """ .+ strReplace '>' ">" . strReplace '<' "<" . strReplace '&' "&"+++strReplace :: Char -> String -> String -> String+strReplace _ _ [] = []+strReplace x y (z:zs)+ | x == z = y ++ (strReplace x y zs)+ | otherwise = z : (strReplace x y zs)++make_alt_filename :: PatchInfo -> String+make_alt_filename pi@(PatchInfo { is_inverted = False }) =+ fix_up_fname (midtrunc (pi_name pi)++"-"++just_author pi++"-"++BC.unpack (_pi_date pi))+make_alt_filename pi@(PatchInfo { is_inverted = True}) =+ make_alt_filename (pi { is_inverted = False }) ++ "-inverted"++-- This makes darcs-1 (non-hashed repos) filenames, and is also generally used in both in+-- hashed and non-hashed repo code for making patch "hashes"+make_filename :: PatchInfo -> String+make_filename pi =+ showIsoDateTime d++"-"++sha1_a++"-"++sha1PS sha1_me++".gz"+ where b2ps True = BC.pack "t"+ b2ps False = BC.pack "f"+ sha1_me = B.concat [_pi_name pi,+ _pi_author pi,+ _pi_date pi,+ B.concat $ _pi_log pi,+ b2ps $ is_inverted pi]+ d = readPatchDate $ _pi_date pi+ sha1_a = take 5 $ sha1PS $ _pi_author pi++midtrunc :: String -> String+midtrunc s+ | length s < 73 = s+ | otherwise = (take 40 s)++"..."++(reverse $ take 30 $ reverse s)+fix_up_fname :: String -> String+fix_up_fname = map munge_char++munge_char :: Char -> Char+munge_char '*' = '+'+munge_char '?' = '2'+munge_char '>' = '7'+munge_char '<' = '2'+munge_char ' ' = '_'+munge_char '"' = '~'+munge_char '`' = '.'+munge_char '\'' = '.'+munge_char '/' = '1'+munge_char '\\' = '1'+munge_char '!' = '1'+munge_char ':' = '.'+munge_char ';' = ','+munge_char '{' = '~'+munge_char '}' = '~'+munge_char '(' = '~'+munge_char ')' = '~'+munge_char '[' = '~'+munge_char ']' = '~'+munge_char '=' = '+'+munge_char '#' = '+'+munge_char '%' = '8'+munge_char '&' = '6'+munge_char '@' = '9'+munge_char '|' = '1'+munge_char c = c++instance HTML RepoPatchInfo where+ toHtml = htmlPatchInfo+instance Show PatchInfo where+ show pi = renderString (showPatchInfo pi)++-- |Patch is stored between square brackets.+--+-- > [ <patch name>+-- > <patch author>*<patch date>+-- > <patch log (may be empty)> (indented one)+-- > <can have multiple lines in patch log,>+-- > <as long as they're preceded by a space>+-- > <and don't end with a square bracket.>+-- > ]+--+-- note that below I assume the name has no newline in it.+showPatchInfo :: PatchInfo -> Doc+showPatchInfo pi =+ blueText "[" <> packedString (_pi_name pi)+ $$ packedString (_pi_author pi) <> text inverted <> packedString (_pi_date pi)+ <> myunlines (_pi_log pi) <> blueText "] "+ where inverted = if is_inverted pi then "*-" else "**"+ myunlines [] = empty+ myunlines xs = mul xs+ where mul [] = text "\n"+ mul (s:ss) = text "\n " <> packedString s <> mul ss++--+-- Note, Data.ByteString rewrites break ((==) x) into the memchr-based+-- breakByte. For this rule to fire, we keep it in prefix application form+--++readPatchInfo :: B.ByteString -> Maybe (PatchInfo, B.ByteString)+readPatchInfo s | B.null (dropSpace s) = Nothing+readPatchInfo s =+ if BC.head (dropSpace s) /= '[' -- ]+ then Nothing+ else case BC.break ((==) '\n') $ B.tail $ dropSpace s of+ (name,s') ->+ case BC.break ((==) '*') $ B.tail s' of+ (author,s2) ->+ case BC.break (\c->c==']'||c=='\n') $ B.drop 2 s2 of+ (ct,s''') ->+ do (log, s4) <- lines_starting_with_ending_with ' ' ']' $ dn s'''+ return $ (PatchInfo { _pi_date = ct+ , _pi_name = name+ , _pi_author = author+ , _pi_log = log+ , is_inverted = BC.index s2 1 /= '*'+ }, s4)+ where dn x = if B.null x || BC.head x /= '\n' then x else B.tail x++lines_starting_with_ending_with :: Char -> Char -> B.ByteString+ -> Maybe ([B.ByteString],B.ByteString)+lines_starting_with_ending_with st en s = lswew s+ where+ lswew x | B.null x = Nothing+ lswew x =+ if BC.head x == en+ then Just ([], B.tail x)+ else if BC.head x /= st+ then Nothing+ else case BC.break ((==) '\n') $ B.tail x of+ (l,r) -> case lswew $ B.tail r of+ Just (ls,r') -> Just (l:ls,r')+ Nothing ->+ case breakLastPS en l of+ Just (l2,_) ->+ Just ([l2], B.drop (B.length l2+2) x)+ Nothing -> Nothing++htmlPatchInfo :: RepoPatchInfo -> Html+htmlPatchInfo (RPI r pi) =+ toHtml $ (td << patch_link r pi) `above`+ ((td ! [align "right"] << mail_link (just_author pi)) `beside`+ (td << (friendly_d $ _pi_date pi)))++patch_link :: String -> PatchInfo -> Html+patch_link r pi =+ toHtml $ hotlink+ ("darcs?"++r++"**"++make_filename pi)+ [toHtml $ pi_name pi]+mail_link :: String -> Html+mail_link email = toHtml $ hotlink ("mailto:"++email) [toHtml email]
@@ -0,0 +1,346 @@+% Copyright (C) 2004 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\begin{code}+{-# OPTIONS_GHC -cpp #-}+{-# LANGUAGE CPP #-}++#include "gadts.h"++module Darcs.Patch.Match ( PatchMatch, Matcher, MatchFun,+ patch_match, match_pattern,+ apply_matcher, make_matcher,+ parseMatch,+ match_parser, helpOnMatchers,+ ) where++import Text.ParserCombinators.Parsec+import Text.ParserCombinators.Parsec.Expr+import Text.Regex ( mkRegex, matchRegex )+import Data.Maybe ( isJust )+import System.IO.Unsafe ( unsafePerformIO )++import Darcs.Hopefully ( PatchInfoAnd, hopefully, info )+import Darcs.Patch ( Patch, Patchy, list_touched_files, patchcontents )+import Darcs.Patch.Info ( just_name, just_author, make_filename,+ pi_date )+import Darcs.Sealed ( Sealed2(..), seal2 )+import DateMatcher ( parseDateMatcher )++import Darcs.Patch.MatchData ( PatchMatch(..), patch_match )++-- | A type for predicates over patches which do not care about+-- contexts+type MatchFun p = Sealed2 (PatchInfoAnd p) -> Bool++-- | A @Matcher@ is made of a 'MatchFun' which we will use to match+-- patches and a @String@ representing it.+data Matcher p = MATCH String (MatchFun p)++instance Show (Matcher p) where+ show (MATCH s _) = '"':s ++ "\""++make_matcher :: String -> (Sealed2 (PatchInfoAnd p) -> Bool) -> Matcher p+make_matcher s m = MATCH s m++-- | @apply_matcher@ applies a matcher to a patch.+apply_matcher :: Matcher p -> PatchInfoAnd p C(x y) -> Bool+apply_matcher (MATCH _ m) = m . seal2++parseMatch :: Patchy p => PatchMatch -> Either String (MatchFun p)+parseMatch (PatternMatch s) =+ case parse match_parser "match" s of+ Left err -> Left $ "Invalid -"++"-match pattern '"++s+++ "'.\n"++ unlines (map (" "++) $ lines $ show err) -- indent+ Right m -> Right m++match_pattern :: Patchy p => PatchMatch -> Matcher p+match_pattern p@(PatternMatch s) =+ case parseMatch p of+ Left err -> error err+ Right m -> make_matcher s m++trivial :: Patchy p => MatchFun p+trivial = const True+\end{code}++\paragraph{Match}++Currently \verb!--match! accepts six primitive match types, although+there are plans to expand it to match more patterns. Also, note that the+syntax is still preliminary and subject to change.++The first match type accepts a literal string which is checked against+the patch name. The syntax is+\begin{verbatim}+darcs annotate --summary --match 'exact foo+bar'+\end{verbatim}+This is useful for situations where a patch name contains characters that+could be considered special for regular expressions.++In this and the other match types, the argument must be enclosed in double+quotes if it contains spaces. You can escape a quote in the argument with a+backslash; backslash escapes itself, but it is treated literally if followed+by a character other than a double quote or backslash, so it is typically not+necessary to escape a backslash. No such escaping is necessary unless the+argument is enclosed in double quotes.++The second match type accepts a regular expression which is checked against+the patch name. The syntax is+\begin{verbatim}+darcs annotate --summary --match 'name foo'+\end{verbatim}+Note that to match regexp metacharacters, such as \verb|(|, literally, they+must be escaped with backslash along with any embedded double quotes. To+match a literal backslash it must be written quadrupled in general, but often+it need not be escaped, since backslash is only special in regexps when+followed by a metacharacter. In the following example pairs, the first+literal is matched by the second sequence in the match name:+``\verb|"|'':``\verb|\"|'', ``\verb|\|'':``\verb|\\\\|'',+``\verb|\x|'':``\verb|\x|'', ``\verb|(|'':``\verb|\(|''.++The third match type matches the darcs hash for each patch:+\begin{verbatim}+darcs annotate --summary --match \+ 'hash 20040403105958-53a90-c719567e92c3b0ab9eddd5290b705712b8b918ef'+\end{verbatim}+Note you need to provide the full hash string as above.+This is intended to be used, for example, by programs allowing you to view+darcs repositories (e.g.\ CGI scripts like viewCVS).++The fourth match type accepts a regular expression which is checked against+the patch author. The syntax is+\begin{verbatim}+darcs annotate --summary --match 'author foo'+\end{verbatim}++There is also support for matching by date. This is done using commands such as+\begin{verbatim}+darcs annotate --summary --match 'date "last week"'+darcs annotate --summary --match 'date yesterday'+darcs annotate --summary --match 'date "today 14:00"'+darcs annotate --summary --match 'date "tea time yesterday"'+darcs annotate --summary --match 'date "3 days before last year at 17:00"'+darcs changes --from-match 'date "Sat Jun 30 11:31:30 EDT 2004"'+\end{verbatim}++Only English date specifications are supported---specifically you must use+English day and month names. Also, only a limited set of time zones is+supported (compatible with GNU coreutils' date parsing). Unrecognized zones+are treated as UTC, which may result in the timestamps printed in change+entries not being recognized by the date matching. You can avoid this problem+on a POSIX-like system by running darcs in the UTC zone to get the times+initially, e.g.:+\begin{verbatim}+TZ=UTC darcs changes+\end{verbatim}++When matching on the ISO format, a partial date is treated as a range.+English dates can either refer to a specific day (``6 months ago',``day before+yesterday''), or to an interval+from some past date (``last month'') to the present. Putting this all+together, if today is ``2004-07-24'' then the following matches should work:++\begin{tabular}{|ll|}+\hline+\textbf{date} & \textbf{patches selected} \\+\hline+2004 & from 2004-01-01 up to and including 2004-12-31 \\+2004-01 & from 2004-01-01 up to and including 2004-01-31 \\+2004-01-01 & during 2004-01-01 \\+\hline+today & during 2004-07-24 (starting midnight in your timezone) \\+yesterday & during 2004-07-23 \\+6 months ago & during 2004-01-23 \\+\hline+last 6 months & since 2004-01-23 \\+last month & since 2004-06-23 (not 2004-06-01!) \\+last week & since 2004-07-16 \\+\hline+\end{tabular}++For more precise control, you may specify an interval, either+in a small subset of English or+of \htmladdnormallinkfoot{the ISO 8601 format}{http://www.w3.org/TR/NOTE-datetime}.+If you use the ISO format, note that durations, when+specified alone, are interpreted as being relative to the current date and time.+\begin{verbatim}+darcs annotate --summary --match 'date "between 2004-03-12 and last week"'+darcs annotate --summary --match 'date "after 2005"'+darcs annotate --summary --match 'date "in the last 3 weeks"'+darcs annotate --summary --match 'date "P3M/2006-03-17"'+darcs annotate --summary --match 'date "2004-01-02/2006-03-17"'+darcs annotate --summary --match 'date "P2M6D"'+\end{verbatim}++You may also prefer to combine date matching with a more specific pattern.+\begin{verbatim}+darcs annotate --summary --match 'date "last week" && name foo'+\end{verbatim}++The sixth match type accepts a regular expression which is checked against+file paths that the patch touches. The syntax is+\begin{verbatim}+darcs annotate --summary --match 'touch foo/bar.c'+\end{verbatim}++The \verb!--match! pattern can include the logical operators \verb!&&!,+\verb!||! and \verb!not!, as well as grouping of patterns with parentheses.+For example+\begin{verbatim}+darcs annotate --summary --match 'name record && not name overrode'+\end{verbatim}++\begin{code}+match_parser :: Patchy p => CharParser st (MatchFun p)+match_parser = do m <- option trivial submatch+ eof+ return m++submatch :: Patchy p => CharParser st (MatchFun p)+submatch = buildExpressionParser table match <?> "match rule"++table :: OperatorTable Char st (MatchFun p)+table = [ [prefix "not" negate_match,+ prefix "!" negate_match ]+ , [binary "||" or_match,+ binary "or" or_match,+ binary "&&" and_match,+ binary "and" and_match ]+ ]+ where binary name fun =+ Infix (do trystring name+ spaces+ return fun) AssocLeft+ prefix name fun = Prefix $ do trystring name+ spaces+ return fun+ negate_match a p = not (a p)+ or_match m1 m2 p = (m1 p) || (m2 p)+ and_match m1 m2 p = (m1 p) && (m2 p)++trystring :: String -> CharParser st String+trystring s = try $ string s++match :: Patchy p => CharParser st (MatchFun p)+match = between spaces spaces+ (parens submatch+ <|> choice matchers_+ <?> "simple match")+ where matchers_ = map createMatchHelper primitiveMatchers+++createMatchHelper :: (String, String, [String], String -> MatchFun p)+ -> CharParser st (MatchFun p)+createMatchHelper (key,_,_,matcher) =+ do trystring key+ spaces+ q <- quoted+ return $ matcher q++-- FIXME: would this be better defined in Darcs.Commands.Help?+-- | The string that is emitted when the user runs @darcs help --match@.+helpOnMatchers :: String+helpOnMatchers = unlines $+ ["Selecting Patches:",+ "",+ "The --patches option yields patches with names matching an `extended'",+ "regular expression. See regex(7) for details. The --matches option",+ "yields patches that match a logical (Boolean) expression: one or more",+ "primitive expressions combined by grouping (parentheses) and the",+ "complement (not), conjunction (and) and disjunction (or) operators.",+ "The C notation for logic operators (!, && and ||) can also be used.",+ "",+ " --patches=regex is a synonym for --matches='name regex'", + " --from-patch and --to-patch are synonyms for --from-match='name... and --to-match='name...",+ " --from-patch and --to-match can be unproblematically combined:",+ " darcs changes --from-patch='html.*documentation' --to-match='date 20040212'",+ "",+ "The following primitive Boolean expressions are supported:"]+ ++ keywords+ ++ ["", "Here are some examples:"]+ ++ examples+ where -- This type signature exists to appease GHC.+ ps :: [(String, String, [String], String -> MatchFun Patch)]+ ps = primitiveMatchers+ keywords = [showKeyword k d | (k,d,_,_) <- ps]+ examples = [showExample k e | (k,_,es,_) <- ps, e <- es]+ showKeyword keyword description =+ -- FIXME: it would be nice to have a variable name here:+ -- "author REGEX - match against author (email address)"+ -- or "exact STRING - match against exact patch name".+ " " ++ keyword ++ " - " ++ description ++ "."+ showExample keyword example =+ -- FIXME: this string is long, and its not a use case I've+ -- ever seen in practice. Can we use something else,+ -- like "darcs changes --matches"? --twb, 2008-12-28+ " darcs annotate --summary --match "+ ++ "'" ++ keyword ++ " " ++ example ++ "'"++primitiveMatchers :: Patchy p => [(String, String, [String], String -> MatchFun p)]+ -- ^ keyword (operator), help description, list+ -- of examples, matcher function+primitiveMatchers =+ [ ("exact", "check a literal string against the patch name"+ , ["\"Resolve issue17: use dynamic memory allocation.\""]+ , exactmatch )+ , ("name", "check a regular expression against the patch name"+ , ["issue17", "\"^[Rr]esolve issue17\\>\""]+ , mymatch )+ , ("author", "check a regular expression against the author name"+ , ["\"David Roundy\"", "droundy", "droundy@darcs.net"]+ , authormatch )+ , ("hash", "match the darcs hash for a patch"+ , ["20040403105958-53a90-c719567e92c3b0ab9eddd5290b705712b8b918ef"]+ , hashmatch )+ , ("date", "match the patch date"+ , ["\"2006-04-02 22:41\"", "\"tea time yesterday\""]+ , datematch )+ , ("touch", "match file paths for a patch"+ , ["src/foo.c", "src/", "\"src/*.(c|h)\""]+ , touchmatch ) ]++parens :: CharParser st (MatchFun p)+ -> CharParser st (MatchFun p)+parens p = between (string "(") (string ")") p++quoted :: CharParser st String+quoted = between (char '"') (char '"')+ (many $ do { char '\\' -- allow escapes+ ; try (oneOf ['\\', '"']) <|> return '\\'+ }+ <|> noneOf ['"'])+ <|> between spaces spaces (many $ noneOf " ()")+ <?> "string"++mymatch, exactmatch, authormatch, hashmatch, datematch, touchmatch :: Patchy p => String -> MatchFun p++mymatch r (Sealed2 hp) = isJust $ matchRegex (mkRegex r) $ just_name (info hp)++exactmatch r (Sealed2 hp) = r == (just_name (info hp))++authormatch a (Sealed2 hp) = isJust $ matchRegex (mkRegex a) $ just_author (info hp)++hashmatch h (Sealed2 hp) = let rh = make_filename (info hp) in+ (rh == h) || (rh == h++".gz")++datematch d (Sealed2 hp) = let dm = unsafePerformIO $ parseDateMatcher d+ in dm $ pi_date (info hp)++touchmatch r (Sealed2 hp) = let files = list_touched_files $ patchcontents $ hopefully hp+ in or $ map (isJust . matchRegex (mkRegex r)) files+\end{code}
@@ -0,0 +1,28 @@+-- Copyright (C) 2004 David Roundy+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.++module Darcs.Patch.MatchData ( PatchMatch(..), patch_match,+ ) where++data PatchMatch = PatternMatch String+ deriving ( Eq )++instance Show PatchMatch where+ show (PatternMatch m) = "pattern " ++ show m++patch_match :: String -> PatchMatch+patch_match s = PatternMatch s
@@ -0,0 +1,187 @@+-- Copyright (C) 2007 David Roundy+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.++{-# OPTIONS_GHC -cpp -fglasgow-exts -fno-warn-orphans #-}+{-# LANGUAGE CPP #-}+-- , TypeOperators, GADTs, FlexibleContexts #-}++#include "gadts.h"++-- |'NonPatch' and 'Non' patches are patches that store a context as a+-- sequence of patches. See "Darcs.Patch.Real" for example usage.+module Darcs.Patch.Non+ ( NonPatch, Non(..), Nonable(..), unNon,+ showNon, readNon, showNons, readNons,+ add, rem, addP, remP, addPs, remPs, remAddP, remAddPs, remNons,+ (*>), (>*), (*>>), (>>*),+ prop_adjust_twice ) where++import Prelude hiding ( rem )+import Data.List ( delete )+import Control.Monad ( liftM )+import Darcs.Patch.Prim ( Prim, FromPrim(..), ToFromPrim(..), Effect(..),+ showPrim, FileNameFormat(..), sort_coalesceFL )+import Darcs.Patch.Patchy+import Darcs.Patch.ReadMonads ( ParserM, lex_char )+import Darcs.Ordered+import Darcs.Patch.Read ( readPrim )+import Darcs.Patch.Viewing ()+import Darcs.Patch.Permutations ( removeFL, commuteWhatWeCanFL )+import Darcs.Show+import Darcs.Sealed ( Sealed(Sealed) )+import Printer ( Doc, empty, vcat, hiddenPrefix, blueText, redText, ($$) )++--import Darcs.ColorPrinter ( traceDoc )+--import Printer ( greenText )++showNons :: ShowPatch (FL p) => [Non p C(x)] -> Doc+showNons [] = empty+showNons xs = blueText "{{" $$ vcat (map showNon xs) $$ blueText "}}"++showNon :: ShowPatch (FL p) => Non p C(x) -> Doc+showNon (Non c p) = hiddenPrefix "|" (showPatch c)+ $$ hiddenPrefix "|" (blueText ":")+ $$ showPrim NewFormat p++readNons :: (ReadPatch p, ParserM m) => m [Non p C(x)]+readNons = peekfor "{{" rns (return [])+ where rns = peekfor "}}" (return []) $+ do Just (Sealed ps) <- readPatch' False+ lex_char ':'+ Just (Sealed p) <- readPrim NewFormat False+ (Non ps p :) `liftM` rns++readNon :: (ReadPatch p, ParserM m) => m (Maybe (Non p C(x)))+readNon = do Just (Sealed ps) <- readPatch' False+ peekfor ":" (do Just (Sealed p) <- readPatch' False+ return $ Just $ Non ps p)+ (return Nothing)++instance (Commute p, MyEq p) => Eq (Non p C(x)) where+ (Non cx x) == (Non cy y) | IsEq <- cx =\/= cy,+ IsEq <- x =\/= y = True+ | otherwise = False++-- | 'Non' stores a context with a 'Prim' patch.+data Non p C(x) where+ Non :: FL p C(a x) -> Prim C(x y) -> Non p C(a)++-- | Convenience type for non primitive patches+type NonPatch C(x) = Non Prim C(x)++-- | Return as a list the context followed by the primitive patch.+unNon :: FromPrim p => Non p C(x) -> Sealed (FL p C(x))+unNon (Non c x) = Sealed (c +>+ fromPrim x :>: NilFL)++class Nonable p where+ non :: p C(x y) -> Non p C(x)++addP :: (Patchy p, ToFromPrim p) => p C(x y) -> Non p C(y) -> Non p C(x)+addP p n | Just n' <- p >* n = n'+addP p (Non c x) = Non (p:>:c) x++addPs :: (Patchy p, ToFromPrim p) => RL p C(x y) -> Non p C(y) -> Non p C(x)+addPs NilRL n = n+addPs (p:<:ps) n = addPs ps $ addP p n++add :: (Effect q, Patchy p, ToFromPrim p) => q C(x y) -> Non p C(y) -> Non p C(x)+add q = addPs (mapRL_RL fromPrim $ effectRL q)++-- remNons really only works right if the relevant nons are conflicting...+remNons :: (Nonable p, Effect p, Patchy p, ToFromPrim p, ShowPatch p) => [Non p C(x)] -> Non p C(x) -> Non p C(x)+remNons ns (Non c x) = case remNonHelper ns c of+ NilFL :> c' -> Non c' x+ _ -> Non c x++remNonHelper :: (Nonable p, Effect p, Patchy p, ToFromPrim p) => [Non p C(x)] -> FL p C(x y)+ -> (FL Prim :> FL p) C(x y)+remNonHelper [] x = NilFL :> x+remNonHelper ns (c:>:cs)+ | non c `elem` ns = case remNonHelper (map (addP $ invert c) $ delete (non c) ns) cs of+ a :> z -> sort_coalesceFL (effect c+>+a) :> z+ | otherwise = case commuteWhatWeCanFL (c :> cs) of+ b :> c' :> d ->+ case remNonHelper ns b of+ a :> b' -> a :> (b'+>+c':>:d)+remNonHelper _ NilFL = NilFL :> NilFL++remP :: (Patchy p, ToFromPrim p) => p C(x y) -> Non p C(x) -> Maybe (Non p C(y))+remP p n | Just n' <- n *> p = Just n'+remP p (Non pc x) = do c <- removeFL p pc+ return (Non c x)++remPs :: (Patchy p, ToFromPrim p) => FL p C(x y) -> Non p C(x) -> Maybe (Non p C(y))+remPs NilFL n = Just n+remPs (p:>:ps) n = remP p n >>= remPs ps++rem :: (Effect q, Patchy p, ToFromPrim p) => q C(x y) -> Non p C(x) -> Maybe (Non p C(y))+rem q = remPs (mapFL_FL fromPrim $ effect q)++remAddP :: (Patchy p, ToFromPrim p) => p C(x y) -> Non p C(y) -> Non p C(x)+remAddP p n = maybe (addP p n) id $ remP (invert p) n++remAddPs :: (Patchy p, ToFromPrim p) => RL p C(x y) -> Non p C(y) -> Non p C(x)+remAddPs NilRL n = n+remAddPs (x:<:xs) n = remAddPs xs $ remAddP x n++(*>) :: (Patchy p, ToFromPrim p) => Non p C(x) -> p C(x y) -> Maybe (Non p C(y))+n *> p = invert p >* n++(>*) :: (Patchy p, ToFromPrim p) => p C(x y) -> Non p C(y) -> Maybe (Non p C(x))+y >* (Non c x) = case commuteFL (y :> c) of+ Right (c' :> y') -> do+ px' :> _ <- commute (y' :> fromPrim x)+ x' <- toPrim px'+ return (Non c' x')+ _ -> Nothing++(*>>) :: (Effect q, Patchy q, Patchy p, ToFromPrim p) => Non p C(x) -> q C(x y) -> Maybe (Non p C(y))+n *>> p = invert p >>* n++(>>*) :: (Effect q, Patchy p, ToFromPrim p) => q C(x y) -> Non p C(y) -> Maybe (Non p C(x))+q >>* nn = adj (effectRL q) nn+ where adj :: (Patchy p, ToFromPrim p) => RL Prim C(x y) -> Non p C(y) -> Maybe (Non p C(x))+ adj NilRL n = Just n+ adj (x:<:xs) n = fromPrim x >* n >>= adj xs++prop_adjust_twice :: (Patchy p, ToFromPrim p) => p C(x y) -> Non p C(y) -> Maybe Doc+prop_adjust_twice p n =+ do n' <- p >* n+ case n' *> p of+ Nothing -> Just (redText "prop_adjust_inverse 1")+ Just n'' | n'' /= n -> Just (redText "prop_adjust_inverse 2")+ _ -> case n *> invert p of+ Nothing -> Just (redText "prop_adjust_inverse 3")+ Just n'' | n'' /= n' -> Just (redText "prop_adjust_inverse 4")+ _ -> case invert p >* n' of+ Nothing -> Just (redText "prop_adjust_inverse 5")+ Just n'' | n'' /= n -> Just (redText "prop_adjust_inverse 6")+ _ -> Nothing+ ++instance Nonable Prim where+ non = Non NilFL++instance Show2 p => Show (Non p C(x)) where+ showsPrec = showsPrec1++instance Show2 p => Show1 (Non p) where+ showsPrec1 d (Non cs p) = showParen (d > app_prec) $ showString "Non " .+ showsPrec2 (app_prec + 1) cs . showString " " .+ showsPrec (app_prec + 1) p++instance Patchy Prim
@@ -0,0 +1,230 @@+-- Copyright (C) 2007 David Roundy+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.++{-# OPTIONS_GHC -cpp -fglasgow-exts #-}+{-# LANGUAGE CPP #-}+-- , TypeOperators, GADTs #-}++#include "gadts.h"++module Darcs.Patch.Patchy ( Patchy,+ Apply, apply, applyAndTryToFix, applyAndTryToFixFL,+ mapMaybeSnd,+ Commute(..), commuteFL, commuteRL, commuteRLFL,+ mergeFL,+ ShowPatch(..),+ ReadPatch, readPatch', bracketedFL, peekfor,+ Invert(..), invertFL, invertRL ) where++import Control.Monad ( liftM )+import Data.Maybe ( fromJust )+import Data.Word ( Word8 )+import Data.List ( nub )++import Darcs.SlurpDirectory ( Slurpy )+import Darcs.Sealed ( Sealed(..), Sealed2(..), seal2 )+import Darcs.Patch.ReadMonads ( ParserM, lex_eof, peek_input, my_lex, work, alter_input )+import Darcs.Ordered+import Printer ( Doc, (<>), text )+import Darcs.Lock ( writeDocBinFile, gzWriteDocFile )+import Darcs.IO ( WriteableDirectory )+import Darcs.Flags ( DarcsFlag )+import English ( plural, Noun(Noun) )++import ByteStringUtils ( ifHeadThenTail, dropSpace )+import qualified Data.ByteString.Char8 as BC (pack, ByteString)++--import Darcs.ColorPrinter ( traceDoc )+--import Printer ( greenText, ($$) )++class (Apply p, Commute p, ShowPatch p, ReadPatch p, Invert p) => Patchy p where+-- instance (ShowPatch p, Invert p) => Patchy p where++class Apply p where+ apply :: WriteableDirectory m => [DarcsFlag] -> p C(x y) -> m ()+ apply _ p = do mp' <- applyAndTryToFix p+ case mp' of+ Nothing -> return ()+ Just (e, _) -> fail $ "Unable to apply a patch: " ++ e+ applyAndTryToFix :: WriteableDirectory m => p C(x y) -> m (Maybe (String, p C(x y)))+ applyAndTryToFix p = do apply [] p; return Nothing+ applyAndTryToFixFL :: WriteableDirectory m => p C(x y) -> m (Maybe (String, FL p C(x y)))+ applyAndTryToFixFL p = mapMaybeSnd (:>:NilFL) `liftM` applyAndTryToFix p++mapMaybeSnd :: (a -> b) -> Maybe (c, a) -> Maybe (c, b)+mapMaybeSnd f (Just (a,b)) = Just (a,f b)+mapMaybeSnd _ Nothing = Nothing++class Commute p where+ commute :: (p :> p) C(x y) -> Maybe ((p :> p) C(x y))+ commutex :: (p :< p) C(x y) -> Maybe ((p :< p) C(x y))+ commute (x :> y) = do x' :< y' <- commutex (y :< x)+ return (y' :> x')+ commutex (x :< y) = do x' :> y' <- commute (y :> x)+ return (y' :< x')+ merge :: (p :\/: p) C(x y) -> (p :/\: p) C(x y)+ list_touched_files :: p C(x y) -> [FilePath]++class Commute p => ShowPatch p where+ showPatch :: p C(x y) -> Doc+ showNicely :: p C(x y) -> Doc+ showNicely = showPatch+ showContextPatch :: Slurpy -> p C(x y) -> Doc+ showContextPatch _ p = showPatch p+ description :: p C(x y) -> Doc+ description = showPatch+ summary :: p C(x y) -> Doc+ summary = showPatch+ writePatch :: FilePath -> p C(x y) -> IO ()+ writePatch f p = writeDocBinFile f $ showPatch p <> text "\n"+ gzWritePatch :: FilePath -> p C(x y) -> IO ()+ gzWritePatch f p = gzWriteDocFile f $ showPatch p <> text "\n"+ thing :: p C(x y) -> String+ thing _ = "patch"+ things :: p C(x y) -> String+ things x = plural (Noun $ thing x) ""++class ReadPatch p where+ readPatch'+ :: ParserM m => Bool -> m (Maybe (Sealed (p C(x ))))++class MyEq p => Invert p where+ invert :: p C(x y) -> p C(y x)+ identity :: p C(x x)+ sloppyIdentity :: p C(x y) -> EqCheck C(x y)+ sloppyIdentity p = identity =\/= p++instance Apply p => Apply (FL p) where+ apply _ NilFL = return ()+ apply opts (p:>:ps) = apply opts p >> apply opts ps+ applyAndTryToFix NilFL = return Nothing+ applyAndTryToFix (p:>:ps) = do mp <- applyAndTryToFixFL p+ mps <- applyAndTryToFix ps+ return $ case (mp,mps) of+ (Nothing, Nothing) -> Nothing+ (Just (e,p'),Nothing) -> Just (e,p'+>+ps)+ (Nothing, Just (e,ps')) -> Just (e,p:>:ps')+ (Just (e,p'), Just (es,ps')) ->+ Just (unlines [e,es], p'+>+ps')++instance Commute p => Commute (FL p) where+ commute (NilFL :> x) = Just (x :> NilFL)+ commute (x :> NilFL) = Just (NilFL :> x)+ commute (xs :> ys) = do ys' :> rxs' <- commuteRLFL (reverseFL xs :> ys)+ return $ ys' :> reverseRL rxs'+ merge (NilFL :\/: x) = x :/\: NilFL+ merge (x :\/: NilFL) = NilFL :/\: x+ merge ((x:>:xs) :\/: ys) = fromJust $ do ys' :/\: x' <- return $ mergeFL (x :\/: ys)+ xs' :/\: ys'' <- return $ merge (ys' :\/: xs)+ return (ys'' :/\: (x' :>: xs'))+ list_touched_files xs = nub $ concat $ mapFL list_touched_files xs++mergeFL :: Commute p => (p :\/: FL p) C(x y) -> (FL p :/\: p) C(x y)+mergeFL (p :\/: NilFL) = NilFL :/\: p+mergeFL (p :\/: (x :>: xs)) = fromJust $ do x' :/\: p' <- return $ merge (p :\/: x)+ xs' :/\: p'' <- return $ mergeFL (p' :\/: xs)+ return ((x' :>: xs') :/\: p'')++commuteRLFL :: Commute p => (RL p :> FL p) C(x y) -> Maybe ((FL p :> RL p) C(x y))+commuteRLFL (NilRL :> ys) = Just (ys :> NilRL)+commuteRLFL (xs :> NilFL) = Just (NilFL :> xs)+commuteRLFL (xs :> y :>: ys) = do y' :> xs' <- commuteRL (xs :> y)+ ys' :> xs'' <- commuteRLFL (xs' :> ys)+ return (y' :>: ys' :> xs'')++commuteRL :: Commute p => (RL p :> p) C(x y) -> Maybe ((p :> RL p) C(x y))+commuteRL (z :<: zs :> w) = do w' :> z' <- commute (z :> w)+ w'' :> zs' <- commuteRL (zs :> w')+ return (w'' :> z' :<: zs')+commuteRL (NilRL :> w) = Just (w :> NilRL)++commuteFL :: Commute p => (p :> FL p) C(x y) -> Either (Sealed2 p) ((FL p :> p) C(x y))+commuteFL (p :> NilFL) = Right (NilFL :> p)+commuteFL (q :> p :>: ps) = case commute (q :> p) of+ Just (p' :> q') ->+ case commuteFL (q' :> ps) of+ Right (ps' :> q'') -> Right (p' :>: ps' :> q'')+ Left l -> Left l+ Nothing -> Left $ seal2 p++instance ReadPatch p => ReadPatch (FL p) where+ readPatch' want_eof = Just `liftM` read_patches+ where read_patches :: ParserM m => m (Sealed (FL p C(x )))+ read_patches = do --tracePeek "starting FL read"+ mp <- readPatch' False+ case mp of+ Just (Sealed p) -> do --tracePeek "found one patch"+ Sealed ps <- read_patches+ return $ Sealed (p:>:ps)+ Nothing -> if want_eof+ then do --tracePeek "no more patches"+ unit' <- lex_eof+ case unit' of+ () -> return $ Sealed NilFL+ else do --tracePeek "no more patches"+ return $ Sealed NilFL+-- tracePeek x = do y <- peek_input+-- traceDoc (greenText x $$ greenText (show $ sal_to_string y)) return ()++{-# INLINE bracketedFL #-}+bracketedFL :: (ReadPatch p, ParserM m) =>+ Word8 -> Word8 -> m (Maybe (Sealed (FL p C(x))))+bracketedFL pre post =+ peekforw pre bfl (return Nothing)+ where bfl :: (ReadPatch p, ParserM m) => m (Maybe (Sealed (FL p C(x))))+ bfl = peekforw post (return $ Just $ Sealed NilFL)+ (do Just (Sealed p) <- readPatch' False+ Just (Sealed ps) <- bfl+ return $ Just $ Sealed (p:>:ps))++{-# INLINE peekforw #-}+peekforw :: ParserM m => Word8 -> m a -> m a -> m a+peekforw w ifstr ifnot = do s <- peek_input+ case ifHeadThenTail w $ dropSpace s of+ Just s' -> alter_input (const s') >> ifstr+ Nothing -> ifnot++peekforPS :: ParserM m => BC.ByteString -> m a -> m a -> m a+peekforPS ps ifstr ifnot = do s <- peek_input+ case ((ps ==) . fst) `fmap` my_lex s of+ Just True -> work my_lex >> ifstr+ _ -> ifnot++{-# INLINE peekfor #-}+peekfor :: ParserM m => String -> m a -> m a -> m a+peekfor = peekforPS . BC.pack++instance Apply p => Apply (RL p) where+ apply _ NilRL = return ()+ apply opts (p:<:ps) = apply opts ps >> apply opts p+instance Commute p => Commute (RL p) where+ commute (xs :> ys) = do fys' :> xs' <- commuteRLFL (xs :> reverseRL ys)+ return (reverseFL fys' :> xs')+ merge (x :\/: y) = case merge (reverseRL x :\/: reverseRL y) of+ (ry' :/\: rx') -> reverseFL ry' :/\: reverseFL rx'+ list_touched_files = list_touched_files . reverseRL+instance ReadPatch p => ReadPatch (RL p) where+ readPatch' want_eof = do Just (Sealed fl) <- readPatch' want_eof+ return $ Just $ Sealed $ reverseFL fl++invertFL :: Invert p => FL p C(x y) -> RL p C(y x)+invertFL NilFL = NilRL+invertFL (x:>:xs) = invert x :<: invertFL xs++invertRL :: Invert p => RL p C(x y) -> FL p C(y x)+invertRL NilRL = NilFL+invertRL (x:<:xs) = invert x :>: invertRL xs
@@ -0,0 +1,232 @@+-- Copyright (C) 2002-2003 David Roundy+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.++{-# OPTIONS_GHC -cpp -fglasgow-exts -fno-warn-orphans #-}+{-# LANGUAGE CPP #-}+-- , TypeOperators, PatternGuards #-}++#include "gadts.h"++module Darcs.Patch.Permutations ( removeFL, removeRL, removeCommon,+ commuteWhatWeCanFL, commuteWhatWeCanRL,+ genCommuteWhatWeCanRL,+ partitionFL, partitionRL,+ head_permutationsFL, head_permutationsRL,+ headPermutationsFL,+ remove_subsequenceFL, remove_subsequenceRL ) where++import Data.Maybe ( catMaybes )+import Darcs.Patch.Patchy ( Commute, commute, commuteFL, commuteRL, Invert(..), invertFL, invertRL )+import Darcs.Ordered+#include "impossible.h"++-- |split an 'FL' into "left" and "right" lists according to a predicate, using commutation as necessary.+-- If a patch does satisfy the predicate but cannot be commuted past one that does not satisfy+-- the predicate, it goes in the "right" list.+partitionFL :: Commute p+ => (FORALL(u v) p C(u v) -> Bool) -- ^predicate; if true we would like the patch in the "left" list+ -> FL p C(x y) -- ^input 'FL'+ -> (FL p :> FL p) C(x y) -- ^"left" and "right" results++-- optimise by using an accumulating parameter to track all the "right" patches that we've found so far+partitionFL' :: Commute p+ => (FORALL(u v) p C(u v) -> Bool)+ -> RL p C(x z) -- the "right" patches found so far+ -> FL p C(z y)+ -> (FL p :> FL p) C(x y)++partitionFL keepleft ps = partitionFL' keepleft NilRL ps++partitionFL' _ qs NilFL = NilFL :> reverseRL qs+partitionFL' keepleft qs (p :>: ps)+ | keepleft p,+ Just (p' :> qs') <- commuteRL (qs :> p)+ = case partitionFL' keepleft qs' ps of+ a :> b -> p' :>: a :> b+ | otherwise = partitionFL' keepleft (p :<: qs) ps++-- |split an 'RL' into "left" and "right" lists according to a predicate, using commutation as necessary.+-- If a patch does satisfy the predicate but cannot be commuted past one that does not satisfy+-- the predicate, it goes in the "left" list.+partitionRL :: Commute p+ => (FORALL(u v) p C(u v) -> Bool) -- ^predicate; if true we would like the patch in the "right" list+ -> RL p C(x y) -- ^input 'RL'+ -> (RL p :> RL p) C(x y) -- ^"left" and "right" results++-- optimise by using an accumulating parameter to track all the "left" patches that we've found so far+partitionRL' :: Commute p+ => (FORALL(u v) p C(u v) -> Bool)+ -> RL p C(x z)+ -> FL p C(z y) -- the "left" patches found so far+ -> (RL p :> RL p) C(x y)++partitionRL keepright ps = partitionRL' keepright ps NilFL++partitionRL' _ NilRL qs = reverseFL qs :> NilRL++partitionRL' keepright (p :<: ps) qs+ | keepright p,+ Right (qs' :> p') <- commuteFL (p :> qs)+ = case partitionRL' keepright ps qs' of+ a :> b -> a :> p' :<: b+ | otherwise = partitionRL' keepright ps (p :>: qs)++commuteWhatWeCanFL :: Commute p => (p :> FL p) C(x y) -> (FL p :> p :> FL p) C(x y)+commuteWhatWeCanFL (p :> x :>: xs) =+ case commute (p :> x) of+ Nothing -> case commuteWhatWeCanFL (x :> xs) of+ xs1 :> x' :> xs2 -> case commuteWhatWeCanFL (p :> xs1) of+ xs1' :> p' :> xs2' -> xs1' :> p' :> xs2' +>+ x' :>: xs2+ Just (x' :> p') -> case commuteWhatWeCanFL (p' :> xs) of+ a :> p'' :> c -> x' :>: a :> p'' :> c+commuteWhatWeCanFL (y :> NilFL) = NilFL :> y :> NilFL++commuteWhatWeCanRL :: Commute p => (RL p :> p) C(x y) -> (RL p :> p :> RL p) C(x y)+commuteWhatWeCanRL = genCommuteWhatWeCanRL commute++genCommuteWhatWeCanRL :: (FORALL(a b) ((p :> p) C(a b) -> Maybe ((p :> p) C(a b))))+ -> (RL p :> p) C(x y) -> (RL p :> p :> RL p) C(x y)+genCommuteWhatWeCanRL com (x :<: xs :> p) =+ case com (x :> p) of+ Nothing -> case genCommuteWhatWeCanRL com (xs :> x) of+ xs1 :> x' :> xs2 -> case genCommuteWhatWeCanRL com (xs2 :> p) of+ xs1' :> p' :> xs2' -> xs1' +<+ x' :<: xs1 :> p' :> xs2'+ Just (p' :> x') -> case genCommuteWhatWeCanRL com (xs :> p') of+ a :> p'' :> c -> a :> p'' :> x' :<: c+genCommuteWhatWeCanRL _ (NilRL :> y) = NilRL :> y :> NilRL+++removeCommon :: (MyEq p, Commute p) => (FL p :\/: FL p) C(x y) -> (FL p :\/: FL p) C(x y)+removeCommon (xs :\/: NilFL) = xs :\/: NilFL+removeCommon (NilFL :\/: xs) = NilFL :\/: xs+removeCommon (xs :\/: ys) = rc xs (headPermutationsFL ys)+ where rc :: (MyEq p, Commute p) => FL p C(x y) -> [(p:>FL p) C(x z)] -> (FL p :\/: FL p) C(y z)+ rc nms ((n:>ns):_) | Just ms <- removeFL n nms = removeCommon (ms :\/: ns)+ rc ms [n:>ns] = ms :\/: n:>:ns+ rc ms (_:nss) = rc ms nss+ rc _ [] = impossible -- because we already checked for NilFL case++-- | 'removeFL' @x xs@ removes @x@ from @xs@ if @x@ can be commuted to its head.+-- Otherwise it returns 'Nothing'+removeFL :: (MyEq p, Commute p) => p C(x y) -> FL p C(x z) -> Maybe (FL p C(y z))+removeFL x xs = r x $ headPermutationsFL xs+ where r :: (MyEq p, Commute p) => p C(x y) -> [(p:>FL p) C(x z)] -> Maybe (FL p C(y z))+ r _ [] = Nothing+ r z ((z':>zs):zss) | IsEq <- z =\/= z' = Just zs+ | otherwise = r z zss++-- | 'removeRL' is like 'removeFL' except with 'RL'+removeRL :: (MyEq p, Commute p) => p C(y z) -> RL p C(x z) -> Maybe (RL p C(x y))+removeRL x xs = r x $ head_permutationsRL xs+ where r :: (MyEq p, Commute p) => p C(y z) -> [RL p C(x z)] -> Maybe (RL p C(x y))+ r z ((z':<:zs):zss) | IsEq <- z =/\= z' = Just zs+ | otherwise = r z zss+ r _ _ = Nothing++-- | 'remove_subsequenceFL' @ab abc@ returns @Just c'@ where all the patches in+-- @ab@ have been commuted out of it, if possible. If this is not possible+-- for any reason (the set of patches @ab@ is not actually a subset of @abc@,+-- or they can't be commuted out) we return 'Nothing'.+remove_subsequenceFL :: (MyEq p, Commute p) => FL p C(a b)+ -> FL p C(a c) -> Maybe (FL p C(b c))+remove_subsequenceFL a b | lengthFL a > lengthFL b = Nothing+ | otherwise = rsFL a b+ where rsFL :: (MyEq p, Commute p) => FL p C(a b) -> FL p C(a c) -> Maybe (FL p C(b c))+ rsFL NilFL ys = Just ys+ rsFL (x:>:xs) yys = removeFL x yys >>= remove_subsequenceFL xs++-- | 'remove_subsequenceRL' is like @remove_subsequenceFL@ except that it works+-- on 'RL'+remove_subsequenceRL :: (MyEq p, Commute p) => RL p C(ab abc)+ -> RL p C(a abc) -> Maybe (RL p C(a ab))+remove_subsequenceRL a b | lengthRL a > lengthRL b = Nothing+ | otherwise = rsRL a b+ where rsRL :: (MyEq p, Commute p) => RL p C(ab abc) -> RL p C(a abc) -> Maybe (RL p C(a ab))+ rsRL NilRL ys = Just ys+ rsRL (x:<:xs) yys = removeRL x yys >>= remove_subsequenceRL xs++-- | This is a minor variant of 'headPermutationsFL' with each permutation+-- is simply returned as a 'FL'+head_permutationsFL :: Commute p => FL p C(x y) -> [FL p C(x y)]+head_permutationsFL ps = map (\ (x:>xs) -> x:>:xs) $ headPermutationsFL ps++-- | 'headPermutationsFL' @p:>:ps@ returns all the permutations of the list+-- in which one element of @ps@ is commuted past @p@+--+-- Suppose we have a sequence of patches+--+-- > X h a y s-t-c k+--+-- Suppose furthermore that the patch @c@ depends on @t@, which in turn+-- depends on @s@. This function will return+--+-- > X :> h a y s t c k+-- > h :> X a y s t c k+-- > a :> X h y s t c k+-- > y :> X h a s t c k+-- > s :> X h a y t c k+-- > k :> X h a y s t c+headPermutationsFL :: Commute p => FL p C(x y) -> [(p :> FL p) C(x y)]+headPermutationsFL NilFL = []+headPermutationsFL (p:>:ps) =+ (p:>ps) : catMaybes (map (swapfirstFL.(p:>)) $ headPermutationsFL ps)+ where swapfirstFL (p1:>p2:>xs) = do p2':>p1' <- commute (p1:>p2)+ Just $ p2':>p1':>:xs++-- | 'head_permutationsRL' is like 'headPermutationsFL', except that we+-- operate on an 'RL' (in other words, we are pushing things to the end of a+-- patch sequence instead of to the beginning).+head_permutationsRL :: Commute p => RL p C(x y) -> [RL p C(x y)]+head_permutationsRL NilRL = []+head_permutationsRL (p:<:ps) =+ (p:<:ps) : catMaybes (map (swapfirstRL.(p:<:)) $ head_permutationsRL ps)+ where swapfirstRL (p1:<:p2:<:xs) = do p1':>p2' <- commute (p2:>p1)+ Just $ p2':<:p1':<:xs+ swapfirstRL _ = Nothing++instance (MyEq p, Commute p) => MyEq (FL p) where+ a =\/= b | lengthFL a /= lengthFL b = NotEq+ | otherwise = cmpSameLength a b+ where cmpSameLength :: FL p C(x y) -> FL p C(x z) -> EqCheck C(y z)+ cmpSameLength (x:>:xs) xys | Just ys <- removeFL x xys = cmpSameLength xs ys+ cmpSameLength NilFL NilFL = IsEq+ cmpSameLength _ _ = NotEq+ xs =/\= ys = reverseFL xs =/\= reverseFL ys++instance (Invert p, Commute p) => Invert (FL p) where+ invert = reverseRL . invertFL+ identity = NilFL+ sloppyIdentity NilFL = IsEq+ sloppyIdentity (x:>:xs) | IsEq <- sloppyIdentity x = sloppyIdentity xs+ sloppyIdentity _ = NotEq++instance (MyEq p, Commute p) => MyEq (RL p) where+ unsafeCompare = bug "Buggy use of unsafeCompare on RL"+ a =/\= b | lengthRL a /= lengthRL b = NotEq+ | otherwise = cmpSameLength a b+ where cmpSameLength :: RL p C(x y) -> RL p C(w y) -> EqCheck C(x w)+ cmpSameLength (x:<:xs) xys | Just ys <- removeRL x xys = cmpSameLength xs ys+ cmpSameLength NilRL NilRL = IsEq+ cmpSameLength _ _ = NotEq+ xs =\/= ys = reverseRL xs =\/= reverseRL ys++instance (Commute p, Invert p) => Invert (RL p) where+ invert = reverseFL . invertRL+ identity = NilRL+ sloppyIdentity NilRL = IsEq+ sloppyIdentity (x:<:xs) | IsEq <- sloppyIdentity x = sloppyIdentity xs+ sloppyIdentity _ = NotEq
@@ -0,0 +1,1130 @@+% Copyright (C) 2002-2003,2007 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\begin{code}+{-# OPTIONS_GHC -cpp -fglasgow-exts #-}+{-# LANGUAGE CPP #-}+-- , MagicHash, TypeOperators, GADTs, PatternGuards #-}++#include "gadts.h"++module Darcs.Patch.Prim+ ( Prim(..), IsConflictedPrim(IsC), ConflictState(..), showPrim,+ DirPatchType(..), FilePatchType(..),+ CommuteFunction, Perhaps(..),+ null_patch, nullP, is_null_patch,+ is_identity,+ formatFileName, FileNameFormat(..),+ adddir, addfile, binary, changepref,+ hunk, move, rmdir, rmfile, tokreplace,+ is_addfile, is_hunk, is_binary, is_setpref,+ is_similar, is_adddir, is_filepatch,+ canonize, try_to_shrink, modernizePrim,+ subcommutes, sort_coalesceFL, join,+ try_tok_internal,+ try_shrinking_inverse,+ n_fn,+ FromPrim(..), FromPrims(..), ToFromPrim(..),+ Conflict(..), Effect(..), commute_no_conflictsFL, commute_no_conflictsRL+ )+ where++import Prelude hiding ( pi )+import Control.Monad ( MonadPlus, msum, mzero, mplus )+import Data.Maybe ( isNothing )+#ifndef GADT_WITNESSES+import Data.Map ( elems, fromListWith, mapWithKey )+#endif++import ByteStringUtils ( substrPS, fromPS2Hex)+import qualified Data.ByteString as B (ByteString, length, null, head, take, concat, drop)+import qualified Data.ByteString.Char8 as BC (break, pack)++import Darcs.Patch.FileName ( FileName, fn2ps, fn2fp, fp2fn, norm_path,+ movedirfilename, encode_white )+import Darcs.Ordered+import Darcs.Sealed ( Sealed, unseal )+import Darcs.Patch.Patchy ( Invert(..), Commute(..) )+import Darcs.Patch.Permutations () -- for Invert instance of FL+import Darcs.Show+import Darcs.Utils ( nubsort )+import Lcs ( getChanges )+import RegChars ( regChars )+import Printer ( Doc, vcat, packedString, Color(Cyan,Magenta), lineColor,+ text, userchunk, invisibleText, invisiblePS, blueText,+ ($$), (<+>), (<>), prefix, userchunkPS,+ )+import GHC.Base (unsafeCoerce#)+#include "impossible.h"++data Prim C(x y) where+ Move :: !FileName -> !FileName -> Prim C(x y)+ DP :: !FileName -> !(DirPatchType C(x y)) -> Prim C(x y)+ FP :: !FileName -> !(FilePatchType C(x y)) -> Prim C(x y)+ Split :: FL Prim C(x y) -> Prim C(x y)+ Identity :: Prim C(x x)+ ChangePref :: !String -> !String -> !String -> Prim C(x y)++data FilePatchType C(x y) = RmFile | AddFile+ | Hunk !Int [B.ByteString] [B.ByteString]+ | TokReplace !String !String !String+ | Binary B.ByteString B.ByteString+ deriving (Eq,Ord)++data DirPatchType C(x y) = RmDir | AddDir+ deriving (Eq,Ord)++instance MyEq FilePatchType where+ unsafeCompare a b = a == unsafeCoerce# b++instance MyEq DirPatchType where+ unsafeCompare a b = a == unsafeCoerce# b++null_patch :: Prim C(x x)+null_patch = Identity++is_null_patch :: Prim C(x y) -> Bool+is_null_patch (FP _ (Binary x y)) = B.null x && B.null y+is_null_patch (FP _ (Hunk _ [] [])) = True+is_null_patch Identity = True+is_null_patch _ = False++nullP :: Prim C(x y) -> EqCheck C(x y)+nullP = sloppyIdentity++is_identity :: Prim C(x y) -> EqCheck C(x y)+is_identity (FP _ (Binary old new)) | old == new = unsafeCoerce# IsEq+is_identity (FP _ (Hunk _ old new)) | old == new = unsafeCoerce# IsEq+is_identity (FP _ (TokReplace _ old new)) | old == new = unsafeCoerce# IsEq+is_identity (Move old new) | old == new = unsafeCoerce# IsEq+is_identity Identity = IsEq+is_identity _ = NotEq++-- FIXME: The following code needs to be moved.++-- | Tells you if two patches are in the same category, human-wise.+-- Currently just returns true if they are filepatches on the same+-- file.+is_similar :: Prim C(x y) -> Prim C(a b) -> Bool+is_similar (FP f _) (FP f' _) = f == f'+is_similar (DP f _) (DP f' _) = f == f'+is_similar _ _ = False++is_addfile :: Prim C(x y) -> Bool+is_addfile (FP _ AddFile) = True+is_addfile _ = False++is_adddir :: Prim C(x y) -> Bool+is_adddir (DP _ AddDir) = True+is_adddir _ = False++is_hunk :: Prim C(x y) -> Bool+is_hunk (FP _ (Hunk _ _ _)) = True+is_hunk _ = False++is_binary :: Prim C(x y) -> Bool+is_binary (FP _ (Binary _ _)) = True+is_binary _ = False++is_setpref :: Prim C(x y) -> Bool+is_setpref (ChangePref _ _ _) = True+is_setpref _ = False++addfile :: FilePath -> Prim C(x y)+rmfile :: FilePath -> Prim C(x y)+adddir :: FilePath -> Prim C(x y)+rmdir :: FilePath -> Prim C(x y)+move :: FilePath -> FilePath -> Prim C(x y)+changepref :: String -> String -> String -> Prim C(x y)+hunk :: FilePath -> Int -> [B.ByteString] -> [B.ByteString] -> Prim C(x y)+tokreplace :: FilePath -> String -> String -> String -> Prim C(x y)+binary :: FilePath -> B.ByteString -> B.ByteString -> Prim C(x y)++evalargs :: (a -> b -> c) -> a -> b -> c+evalargs f x y = (f $! x) $! y++addfile f = FP (fp2fn $ n_fn f) AddFile+rmfile f = FP (fp2fn $ n_fn f) RmFile+adddir d = DP (fp2fn $ n_fn d) AddDir+rmdir d = DP (fp2fn $ n_fn d) RmDir+move f f' = Move (fp2fn $ n_fn f) (fp2fn $ n_fn f')+changepref p f t = ChangePref p f t+hunk f line old new = evalargs FP (fp2fn $ n_fn f) (Hunk line old new)+tokreplace f tokchars old new =+ evalargs FP (fp2fn $ n_fn f) (TokReplace tokchars old new)+binary f old new = FP (fp2fn $! n_fn f) $ Binary old new++n_fn :: FilePath -> FilePath+n_fn f = "./"++(fn2fp $ norm_path $ fp2fn f)++instance Invert Prim where+ invert Identity = Identity+ invert (FP f RmFile) = FP f AddFile+ invert (FP f AddFile) = FP f RmFile+ invert (FP f (Hunk line old new)) = FP f $ Hunk line new old+ invert (FP f (TokReplace t o n)) = FP f $ TokReplace t n o+ invert (FP f (Binary o n)) = FP f $ Binary n o+ invert (DP d RmDir) = DP d AddDir+ invert (DP d AddDir) = DP d RmDir+ invert (Move f f') = Move f' f+ invert (ChangePref p f t) = ChangePref p t f+ invert (Split ps) = Split $ invert ps+ identity = Identity+ sloppyIdentity Identity = IsEq+ sloppyIdentity _ = NotEq++instance Show (Prim C(x y)) where+ showsPrec d (Move fn1 fn2) = showParen (d > app_prec) $ showString "Move " .+ showsPrec (app_prec + 1) fn1 . showString " " .+ showsPrec (app_prec + 1) fn2+ showsPrec d (DP fn dp) = showParen (d > app_prec) $ showString "DP " .+ showsPrec (app_prec + 1) fn . showString " " .+ showsPrec (app_prec + 1) dp+ showsPrec d (FP fn fp) = showParen (d > app_prec) $ showString "FP " .+ showsPrec (app_prec + 1) fn . showString " " .+ showsPrec (app_prec + 1) fp+ showsPrec d (Split l) = showParen (d > app_prec) $ showString "Split " .+ showsPrec (app_prec + 1) l+ showsPrec _ Identity = showString "Identity"+ showsPrec d (ChangePref p f t) = showParen (d > app_prec) $ showString "ChangePref " .+ showsPrec (app_prec + 1) p . showString " " .+ showsPrec (app_prec + 1) f . showString " " .+ showsPrec (app_prec + 1) t++instance Show2 Prim where+ showsPrec2 = showsPrec++instance Show (FilePatchType C(x y)) where+ showsPrec _ RmFile = showString "RmFile"+ showsPrec _ AddFile = showString "AddFile"+ showsPrec d (Hunk line old new) | all ((==1) . B.length) old && all ((==1) . B.length) new+ = showParen (d > app_prec) $ showString "Hunk " .+ showsPrec (app_prec + 1) line . showString " " .+ showsPrecC old . showString " " .+ showsPrecC new+ where showsPrecC [] = showString "[]"+ showsPrecC ss = showParen True $ showString "packStringLetters " . showsPrec (app_prec + 1) (map B.head ss)+ showsPrec d (Hunk line old new) = showParen (d > app_prec) $ showString "Hunk " .+ showsPrec (app_prec + 1) line . showString " " .+ showsPrec (app_prec + 1) old . showString " " .+ showsPrec (app_prec + 1) new+ showsPrec d (TokReplace t old new) = showParen (d > app_prec) $ showString "TokReplace " .+ showsPrec (app_prec + 1) t . showString " " .+ showsPrec (app_prec + 1) old . showString " " .+ showsPrec (app_prec + 1) new+ -- this case may not work usefully+ showsPrec d (Binary old new) = showParen (d > app_prec) $ showString "Binary " .+ showsPrec (app_prec + 1) old . showString " " .+ showsPrec (app_prec + 1) new++instance Show (DirPatchType C(x y)) where+ showsPrec _ RmDir = showString "RmDir"+ showsPrec _ AddDir = showString "AddDir"++{-+instance Show (Prim C(x y)) where+ show p = renderString (showPrim p) ++ "\n"+-}++data FileNameFormat = OldFormat | NewFormat+formatFileName :: FileNameFormat -> FileName -> Doc+formatFileName OldFormat = packedString . fn2ps+formatFileName NewFormat = text . encode_white . fn2fp++showPrim :: FileNameFormat -> Prim C(a b) -> Doc+showPrim x (FP f AddFile) = showAddFile x f+showPrim x (FP f RmFile) = showRmFile x f+showPrim x (FP f (Hunk line old new)) = showHunk x f line old new+showPrim x (FP f (TokReplace t old new)) = showTok x f t old new+showPrim x (FP f (Binary old new)) = showBinary x f old new+showPrim x (DP d AddDir) = showAddDir x d+showPrim x (DP d RmDir) = showRmDir x d+showPrim x (Move f f') = showMove x f f'+showPrim _ (ChangePref p f t) = showChangePref p f t+showPrim x (Split ps) = showSplit x ps+showPrim _ Identity = blueText "{}"++\end{code}+++\paragraph{Add file}+Add an empty file to the tree.++\verb!addfile filename!+\begin{code}+showAddFile :: FileNameFormat -> FileName -> Doc+showAddFile x f = blueText "addfile" <+> formatFileName x f+\end{code}++\paragraph{Remove file}+Delete a file from the tree.++\verb!rmfile filename!+\begin{code}+showRmFile :: FileNameFormat -> FileName -> Doc+showRmFile x f = blueText "rmfile" <+> formatFileName x f+\end{code}++\paragraph{Move}+Rename a file or directory.++\verb!move oldname newname!+\begin{code}+showMove :: FileNameFormat -> FileName -> FileName -> Doc+showMove x d d' = blueText "move" <+> formatFileName x d <+> formatFileName x d'+\end{code}++\paragraph{Change Pref}+Change one of the preference settings. Darcs stores a number of simple+string settings. Among these are the name of the test script and the name+of the script that must be called prior to packing in a make dist.+\begin{verbatim}+changepref prefname+oldval+newval+\end{verbatim}+\begin{code}+showChangePref :: String -> String -> String -> Doc+showChangePref p f t = blueText "changepref" <+> text p+ $$ userchunk f+ $$ userchunk t+\end{code}++\paragraph{Add dir}+Add an empty directory to the tree.++\verb!adddir filename!+\begin{code}+showAddDir :: FileNameFormat -> FileName -> Doc+showAddDir x d = blueText "adddir" <+> formatFileName x d+\end{code}++\paragraph{Remove dir}+Delete a directory from the tree.++\verb!rmdir filename!+\begin{code}+showRmDir :: FileNameFormat -> FileName -> Doc+showRmDir x d = blueText "rmdir" <+> formatFileName x d+\end{code}+++\paragraph{Hunk}+Replace a hunk (set of contiguous lines) of text with a new+hunk.+\begin{verbatim}+hunk FILE LINE#+-LINE+...++LINE+...+\end{verbatim}+\begin{code}+showHunk :: FileNameFormat -> FileName -> Int -> [B.ByteString] -> [B.ByteString] -> Doc+showHunk x f line old new =+ blueText "hunk" <+> formatFileName x f <+> text (show line)+ $$ lineColor Magenta (prefix "-" (vcat $ map userchunkPS old))+ $$ lineColor Cyan (prefix "+" (vcat $ map userchunkPS new))+\end{code}++\paragraph{Token replace}++Replace a token with a new token. Note that this format means that+whitespace must not be allowed within a token. If you know of a practical+application of whitespace within a token, let me know and I may change+this.+\begin{verbatim}+replace FILENAME [REGEX] OLD NEW+\end{verbatim}+\begin{code}+showTok :: FileNameFormat -> FileName -> String -> String -> String -> Doc+showTok x f t o n = blueText "replace" <+> formatFileName x f+ <+> text "[" <> userchunk t <> text "]"+ <+> userchunk o+ <+> userchunk n+\end{code}++\paragraph{Binary file modification}++Modify a binary file+\begin{verbatim}+binary FILENAME+oldhex+*HEXHEXHEX+...+newhex+*HEXHEXHEX+...+\end{verbatim}+\begin{code}+showBinary :: FileNameFormat -> FileName -> B.ByteString -> B.ByteString -> Doc+showBinary x f o n =+ blueText "binary" <+> formatFileName x f+ $$ invisibleText "oldhex"+ $$ (vcat $ map makeprintable $ break_every 78 $ fromPS2Hex o)+ $$ invisibleText "newhex"+ $$ (vcat $ map makeprintable $ break_every 78 $ fromPS2Hex n)+ where makeprintable ps = invisibleText "*" <> invisiblePS ps++break_every :: Int -> B.ByteString -> [B.ByteString]+break_every n ps | B.length ps < n = [ps]+ | otherwise = B.take n ps : break_every n (B.drop n ps)+\end{code}++\paragraph{Split patch [OBSOLETE!]}+A split patch is similar to a composite patch but rather than being+composed of several patches grouped together, it is created from one+patch that has been split apart, typically through a merge or+commutation.+\begin{verbatim}+(+ <put patches here> (indented two)+)+\end{verbatim}+\begin{code}+showSplit :: FileNameFormat -> FL Prim C(x y) -> Doc+showSplit x ps = blueText "("+ $$ vcat (mapFL (showPrim x) ps)+ $$ blueText ")"++commute_split :: CommuteFunction+commute_split (Split patches :< patch) =+ toPerhaps $ cs (patches :< patch) >>= sc+ where cs :: ((FL Prim) :< Prim) C(x y) -> Maybe ((Prim :< (FL Prim)) C(x y))+ cs (NilFL :< p1) = return (p1 :< NilFL)+ cs (p:>:ps :< p1) = do p1' :< p' <- commutex (p :< p1)+ p1'' :< ps' <- cs (ps :< p1')+ return (p1'' :< p':>:ps')+ sc :: (Prim :< (FL Prim)) C(x y) -> Maybe ((Prim :< Prim) C(x y))+ sc (p1 :< ps) = scFL $ p1 :< (sort_coalesceFL ps)+ where scFL :: (Prim :< (FL Prim)) C(x y)+ -> Maybe ((Prim :< Prim) C(x y))+ scFL (p1' :< (p :>: NilFL)) = return (p1' :< p)+ scFL (p1' :< ps') = return (p1' :< Split ps')+commute_split _ = Unknown++try_to_shrink :: FL Prim C(x y) -> FL Prim C(x y)+try_to_shrink = mapPrimFL try_harder_to_shrink++mapPrimFL :: (FORALL(x y) FL Prim C(x y) -> FL Prim C(x y))+ -> FL Prim C(w z) -> FL Prim C(w z)+mapPrimFL f x =+#ifdef GADT_WITNESSES+ f x+#else +-- an optimisation; break the list up into independent sublists+-- and apply f to each of them+ case mapM toSimple $ mapFL id x of+ Just sx -> foldr (+>+) NilFL $ elems $+ mapWithKey (\ k p -> f (fromSimples k (p NilFL))) $+ fromListWith (flip (.)) $+ map (\ (a,b) -> (a,(b:>:))) sx+ Nothing -> f x++data Simple C(x y) = SFP !(FilePatchType C(x y)) | SDP !(DirPatchType C(x y))+ | SCP String String String+ deriving ( Show )++toSimple :: Prim C(x y) -> Maybe (FileName, Simple C(x y))+toSimple (FP a b) = Just (a, SFP b)+toSimple (DP a AddDir) = Just (a, SDP AddDir)+toSimple (DP _ RmDir) = Nothing -- ordering is trickier with rmdir present+toSimple (Move _ _) = Nothing+toSimple (Split _) = Nothing+toSimple Identity = Nothing+toSimple (ChangePref a b c) = Just (fp2fn "_darcs/prefs/prefs", SCP a b c)++fromSimple :: FileName -> Simple C(x y) -> Prim C(x y)+fromSimple a (SFP b) = FP a b+fromSimple a (SDP b) = DP a b+fromSimple _ (SCP a b c) = ChangePref a b c++fromSimples :: FileName -> FL Simple C(x y) -> FL Prim C(x y)+fromSimples a bs = mapFL_FL (fromSimple a) bs+#endif++try_harder_to_shrink :: FL Prim C(x y) -> FL Prim C(x y)+try_harder_to_shrink x = try_to_shrink2 $ maybe x id (try_shrinking_inverse x)++try_to_shrink2 :: FL Prim C(x y) -> FL Prim C(x y)+try_to_shrink2 psold =+ let ps = sort_coalesceFL psold+ ps_shrunk = shrink_a_bit ps+ in+ if lengthFL ps_shrunk < lengthFL ps+ then try_to_shrink2 ps_shrunk+ else ps_shrunk++try_shrinking_inverse :: FL Prim C(x y) -> Maybe (FL Prim C(x y))+try_shrinking_inverse (x:>:y:>:z)+ | IsEq <- invert x =\/= y = Just z+ | otherwise = case try_shrinking_inverse (y:>:z) of+ Nothing -> Nothing+ Just yz' -> Just $ case try_shrinking_inverse (x:>:yz') of+ Nothing -> x:>:yz'+ Just xyz' -> xyz'+try_shrinking_inverse _ = Nothing++shrink_a_bit :: FL Prim C(x y) -> FL Prim C(x y)+shrink_a_bit NilFL = NilFL+shrink_a_bit (p:>:ps) =+ case try_one NilRL p ps of+ Nothing -> p :>: shrink_a_bit ps+ Just ps' -> ps'++try_one :: RL Prim C(w x) -> Prim C(x y) -> FL Prim C(y z)+ -> Maybe (FL Prim C(w z))+try_one _ _ NilFL = Nothing+try_one sofar p (p1:>:ps) =+ case coalesce (p1 :< p) of+ Just p' -> Just (reverseRL sofar +>+ p':>:NilFL +>+ ps)+ Nothing -> case commutex (p1 :< p) of+ Nothing -> Nothing+ Just (p' :< p1') -> try_one (p1':<:sofar) p' ps++-- | 'sort_coalesceFL' @ps@ coalesces as many patches in @ps@ as+-- possible, sorting the results according to the scheme defined+-- in 'comparePrim'+sort_coalesceFL :: FL Prim C(x y) -> FL Prim C(x y)+sort_coalesceFL = mapPrimFL sort_coalesceFL2++-- | The heart of "sort_coalesceFL"+sort_coalesceFL2 :: FL Prim C(x y) -> FL Prim C(x y)+sort_coalesceFL2 NilFL = NilFL+sort_coalesceFL2 (x:>:xs) | IsEq <- nullP x = sort_coalesceFL2 xs+sort_coalesceFL2 (x:>:xs) | IsEq <- is_identity x = sort_coalesceFL2 xs+sort_coalesceFL2 (x:>:xs) = either id id $ push_coalesce_patch x $ sort_coalesceFL2 xs++-- | 'push_coalesce_patch' @new ps@ is almost like @new :>: ps@ except+-- as an alternative to consing, we first try to coalesce @new@ with+-- the head of @ps@. If this fails, we try again, using commutation+-- to push @new@ down the list until we find a place where either+-- (a) @new@ is @LT@ the next member of the list [see 'comparePrim']+-- (b) commutation fails or+-- (c) coalescing succeeds.+-- The basic principle is to coalesce if we can and cons otherwise.+--+-- As an additional optimization, push_coalesce_patch outputs a Left+-- value if it wasn't able to shrink the patch sequence at all, and+-- a Right value if it was indeed able to shrink the patch sequence.+-- This avoids the O(N) calls to lengthFL that were in the older+-- code.+--+-- Also note that push_coalesce_patch is only ever used (and should+-- only ever be used) as an internal function in in+-- sort_coalesceFL2.+push_coalesce_patch :: Prim C(x y) -> FL Prim C(y z)+ -> Either (FL Prim C(x z)) (FL Prim C(x z))+push_coalesce_patch new NilFL = Left (new:>:NilFL)+push_coalesce_patch new ps@(p:>:ps')+ = case coalesce (p :< new) of+ Just new' | IsEq <- nullP new' -> Right ps'+ | otherwise -> Right $ either id id $ push_coalesce_patch new' ps'+ Nothing -> if comparePrim new p == LT then Left (new:>:ps)+ else case commutex (p :< new) of+ Just (new' :< p') ->+ case push_coalesce_patch new' ps' of+ Right r -> Right $ either id id $+ push_coalesce_patch p' r+ Left r -> Left (p' :>: r)+ Nothing -> Left (new:>:ps)++is_in_directory :: FileName -> FileName -> Bool+is_in_directory d f = iid (fn2fp d) (fn2fp f)+ where iid (cd:cds) (cf:cfs)+ | cd /= cf = False+ | otherwise = iid cds cfs+ iid [] ('/':_) = True+ iid [] [] = True -- Count directory itself as being in directory...+ iid _ _ = False++data Perhaps a = Unknown | Failed | Succeeded a++instance Monad Perhaps where+ (Succeeded x) >>= k = k x+ Failed >>= _ = Failed+ Unknown >>= _ = Unknown+ Failed >> _ = Failed+ (Succeeded _) >> k = k+ Unknown >> k = k+ return = Succeeded+ fail _ = Unknown++instance MonadPlus Perhaps where+ mzero = Unknown+ Unknown `mplus` ys = ys+ Failed `mplus` _ = Failed+ (Succeeded x) `mplus` _ = Succeeded x++toMaybe :: Perhaps a -> Maybe a+toMaybe (Succeeded x) = Just x+toMaybe _ = Nothing++toPerhaps :: Maybe a -> Perhaps a+toPerhaps (Just x) = Succeeded x+toPerhaps Nothing = Failed++clever_commute :: CommuteFunction -> CommuteFunction+clever_commute c (p1:<p2) =+ case c (p1 :< p2) of+ Succeeded x -> Succeeded x+ Failed -> Failed+ Unknown -> case c (invert p2 :< invert p1) of+ Succeeded (p1' :< p2') -> Succeeded (invert p2' :< invert p1')+ Failed -> Failed+ Unknown -> Unknown+--clever_commute c (p1,p2) = c (p1,p2) `mplus`+-- (case c (invert p2,invert p1) of+-- Succeeded (p1', p2') -> Succeeded (invert p2', invert p1')+-- Failed -> Failed+-- Unknown -> Unknown)++speedy_commute :: CommuteFunction+speedy_commute (p1 :< p2) -- Deal with common case quickly!+ | p1_modifies /= Nothing && p2_modifies /= Nothing &&+ p1_modifies /= p2_modifies = Succeeded (unsafeCoerce# p2 :< unsafeCoerce# p1)+ | otherwise = Unknown+ where p1_modifies = is_filepatch p1+ p2_modifies = is_filepatch p2++everything_else_commute :: CommuteFunction+everything_else_commute x = eec x+ where+ eec :: CommuteFunction+ eec (ChangePref p f t :<p1) = Succeeded (unsafeCoerce# p1 :< ChangePref p f t)+ eec (p2 :<ChangePref p f t) = Succeeded (ChangePref p f t :< unsafeCoerce# p2)+ eec (Identity :< p1) = Succeeded (p1 :< Identity)+ eec (p2 :< Identity) = Succeeded (Identity :< p2)+ eec xx =+ msum [+ clever_commute commute_filedir xx+ ,clever_commute commute_split xx+ ]++{-+Note that it must be true that++commutex (A^-1 A, P) = Just (P, A'^-1 A')++and++if commutex (A, B) == Just (B', A')+then commutex (B^-1, A^-1) == Just (A'^-1, B'^-1)+-}++instance Commute Prim where+ merge (y :\/: z) =+ case elegant_merge (y:\/:z) of+ Just (z' :/\: y') -> z' :/\: y'+ Nothing -> error "Commute Prim merge"+ commutex x = toMaybe $ msum [speedy_commute x,+ everything_else_commute x+ ]+ -- Recurse on everything, these are potentially spoofed patches+ list_touched_files (Move f1 f2) = map fn2fp [f1, f2]+ list_touched_files (Split ps) = nubsort $ concat $ mapFL list_touched_files ps+ list_touched_files (FP f _) = [fn2fp f]+ list_touched_files (DP d _) = [fn2fp d]+ list_touched_files (ChangePref _ _ _) = []+ list_touched_files Identity = []++is_filepatch :: Prim C(x y) -> Maybe FileName+is_filepatch (FP f _) = Just f+is_filepatch _ = Nothing++is_superdir :: FileName -> FileName -> Bool+is_superdir d1 d2 = isd (fn2fp d1) (fn2fp d2)+ where isd s1 s2 =+ length s2 >= length s1 + 1 && take (length s1 + 1) s2 == s1 ++ "/"++commute_filedir :: CommuteFunction+commute_filedir (FP f1 p1 :< FP f2 p2) =+ if f1 /= f2 then Succeeded ( FP f2 (unsafeCoerce# p2) :< FP f1 (unsafeCoerce# p1) )+ else commuteFP f1 (p1 :< p2)+commute_filedir (DP d1 p1 :< DP d2 p2) =+ if (not $ is_in_directory d1 d2) && (not $ is_in_directory d2 d1) &&+ d1 /= d2+ then Succeeded ( DP d2 (unsafeCoerce# p2) :< DP d1 (unsafeCoerce# p1) )+ else Failed+commute_filedir (DP d dp :< FP f fp) =+ if not $ is_in_directory d f+ then Succeeded (FP f (unsafeCoerce# fp) :< DP d (unsafeCoerce# dp))+ else Failed++commute_filedir (Move d d' :< FP f2 p2)+ | f2 == d' = Failed+ | (p2 == AddFile || p2 == RmFile) && d == f2 = Failed+ | otherwise = Succeeded (FP (movedirfilename d d' f2) (unsafeCoerce# p2) :< Move d d')+commute_filedir (Move d d' :< DP d2 p2)+ | is_superdir d2 d' || is_superdir d2 d = Failed+ | (p2 == AddDir || p2 == RmDir) && d == d2 = Failed+ | d2 == d' = Failed+ | otherwise = Succeeded (DP (movedirfilename d d' d2) (unsafeCoerce# p2) :< Move d d')+commute_filedir (Move d d' :< Move f f')+ | f == d' || f' == d = Failed+ | f == d || f' == d' = Failed+ | d `is_superdir` f && f' `is_superdir` d' = Failed+ | otherwise =+ Succeeded (Move (movedirfilename d d' f) (movedirfilename d d' f') :<+ Move (movedirfilename f' f d) (movedirfilename f' f d'))++commute_filedir _ = Unknown++type CommuteFunction = FORALL(x y) (Prim :< Prim) C(x y) -> Perhaps ((Prim :< Prim) C(x y))+subcommutes :: [(String, CommuteFunction)]+subcommutes =+ [("speedy_commute", speedy_commute),+ ("commute_filedir", clever_commute commute_filedir),+ ("commute_filepatches", clever_commute commute_filepatches),+ ("commutex", toPerhaps . commutex)+ ]++elegant_merge :: (Prim :\/: Prim) C(x y)+ -> Maybe ((Prim :/\: Prim) C(x y))+elegant_merge (p1 :\/: p2) =+ do p1':>ip2' <- commute (invert p2 :> p1)+ -- The following should be a redundant check+ p1o:>_ <- commute (p2 :> p1')+ IsEq <- return $ p1o =\/= p1+ return (invert ip2' :/\: p1')+\end{code}++It can sometimes be handy to have a canonical representation of a given+patch. We achieve this by defining a canonical form for each patch type,+and a function ``{\tt canonize}'' which takes a patch and puts it into+canonical form. This routine is used by the diff function to create an+optimal patch (based on an LCS algorithm) from a simple hunk describing the+old and new version of a file.+\begin{code}+canonize :: Prim C(x y) -> FL Prim C(x y)+canonize (Split ps) = sort_coalesceFL ps+canonize p | IsEq <- is_identity p = NilFL+canonize (FP f (Hunk line old new)) = canonizeHunk f line old new+canonize p = p :>: NilFL+\end{code}++A simpler, faster (and more generally useful) cousin of canonize is the+coalescing function. This takes two sequential patches, and tries to turn+them into one patch. This function is used to deal with ``split'' patches,+which are created when the commutation of a primitive patch can only be+represented by a composite patch. In this case the resulting composite+patch must return to the original primitive patch when the commutation is+reversed, which a split patch accomplishes by trying to coalesce its+contents each time it is commuted.++\begin{code}+-- | 'coalesce' @p2 :< p1@ tries to combine @p1@ and @p2@ into a single+-- patch without intermediary changes. For example, two hunk patches+-- modifying adjacent lines can be coalesced into a bigger hunk patch.+-- Or a patch which moves file A to file B can be coalesced with a+-- patch that moves file B into file C, yielding a patch that moves+-- file A to file C.+coalesce :: (Prim :< Prim) C(x y) -> Maybe (Prim C(x y))+coalesce (FP f1 _ :< FP f2 _) | f1 /= f2 = Nothing+coalesce (p2 :< p1) | IsEq <- p2 =\/= invert p1 = Just null_patch+coalesce (FP f1 p1 :< FP _ p2) = coalesceFilePrim f1 (p1 :< p2) -- f1 = f2+coalesce (Identity :< p) = Just p+coalesce (p :< Identity) = Just p+coalesce (Split NilFL :< p) = Just p+coalesce (p :< Split NilFL) = Just p+coalesce (Move a b :< Move b' a') | a == a' = Just $ Move b' b+coalesce (Move a b :< FP f AddFile) | f == a = Just $ FP b AddFile+coalesce (Move a b :< DP f AddDir) | f == a = Just $ DP b AddDir+coalesce (FP f RmFile :< Move a b) | b == f = Just $ FP a RmFile+coalesce (DP f RmDir :< Move a b) | b == f = Just $ DP a RmDir+coalesce (ChangePref p f1 t1 :< ChangePref p2 f2 t2) | p == p2 && t2 == f1 = Just $ ChangePref p f2 t1+coalesce _ = Nothing++join :: (Prim :> Prim) C(x y) -> Maybe (Prim C(x y))+join (x :> y) = coalesce (y :< x)+\end{code}++\subsection{File patches}++A file patch is a patch which only modifies a single+file. There are some rules which can be made about file patches in+general, which makes them a handy class.+For example, commutation of two filepatches is trivial if they modify+different files. If they happen to+modify the same file, we'll have to check whether or not they commutex.+\begin{code}+commute_filepatches :: CommuteFunction+commute_filepatches (FP f1 p1 :< FP f2 p2) | f1 == f2 = commuteFP f1 (p1 :< p2)+commute_filepatches _ = Unknown++commuteFP :: FileName -> (FilePatchType :< FilePatchType) C(x y)+ -> Perhaps ((Prim :< Prim) C(x y))+commuteFP f (Hunk line1 [] [] :< p2) =+ seq f $ Succeeded (FP f (unsafeCoerceP p2) :< FP f (Hunk line1 [] []))+commuteFP f (p2 :< Hunk line1 [] []) =+ seq f $ Succeeded (FP f (Hunk line1 [] []) :< FP f (unsafeCoerceP p2))+commuteFP f (Hunk line1 old1 new1 :< Hunk line2 old2 new2) = seq f $+ toPerhaps $ commuteHunk f (Hunk line1 old1 new1 :< Hunk line2 old2 new2)+commuteFP f (TokReplace t o n :< Hunk line2 old2 new2) = seq f $+ case try_tok_replace t o n old2 of+ Nothing -> Failed+ Just old2' ->+ case try_tok_replace t o n new2 of+ Nothing -> Failed+ Just new2' -> Succeeded (FP f (Hunk line2 old2' new2') :<+ FP f (TokReplace t o n))+commuteFP f (TokReplace t o n :< TokReplace t2 o2 n2)+ | seq f $ t /= t2 = Failed+ | o == o2 = Failed+ | n == o2 = Failed+ | o == n2 = Failed+ | n == n2 = Failed+ | otherwise = Succeeded (FP f (TokReplace t2 o2 n2) :<+ FP f (TokReplace t o n))+commuteFP _ _ = Unknown++coalesceFilePrim :: FileName -> (FilePatchType :< FilePatchType) C(x y)+ -> Maybe (Prim C(x y))+coalesceFilePrim f (Hunk line1 old1 new1 :< Hunk line2 old2 new2)+ = coalesceHunk f line1 old1 new1 line2 old2 new2+-- Token replace patches operating right after (or before) AddFile (RmFile)+-- is an identity patch, as far as coalescing is concerned.+coalesceFilePrim f (TokReplace _ _ _ :< AddFile) = Just $ FP f AddFile+coalesceFilePrim f (RmFile :< TokReplace _ _ _) = Just $ FP f RmFile+coalesceFilePrim f (TokReplace t1 o1 n1 :< TokReplace t2 o2 n2)+ | t1 == t2 && n2 == o1 = Just $ FP f $ TokReplace t1 o2 n1+coalesceFilePrim f (Binary m n :< Binary o m')+ | m == m' = Just $ FP f $ Binary o n+coalesceFilePrim _ _ = Nothing+\end{code}++\subsection{Hunks}++The hunk is the simplest patch that has a commuting pattern in which the+commuted patches differ from the originals (rather than simple success or+failure). This makes commuting or merging two hunks a tad tedious.+\begin{code}+commuteHunk :: FileName -> (FilePatchType :< FilePatchType) C(x y)+ -> Maybe ((Prim :< Prim) C(x y))+commuteHunk f (Hunk line2 old2 new2 :< Hunk line1 old1 new1)+ | seq f $ line1 + lengthnew1 < line2 =+ Just (FP f (Hunk line1 old1 new1) :<+ FP f (Hunk (line2 - lengthnew1 + lengthold1) old2 new2))+ | line2 + lengthold2 < line1 =+ Just (FP f (Hunk (line1+ lengthnew2 - lengthold2) old1 new1) :<+ FP f (Hunk line2 old2 new2))+ | line1 + lengthnew1 == line2 &&+ lengthold2 /= 0 && lengthold1 /= 0 && lengthnew2 /= 0 && lengthnew1 /= 0 =+ Just (FP f (Hunk line1 old1 new1) :<+ FP f (Hunk (line2 - lengthnew1 + lengthold1) old2 new2))+ | line2 + lengthold2 == line1 &&+ lengthold2 /= 0 && lengthold1 /= 0 && lengthnew2 /= 0 && lengthnew1 /= 0 =+ Just (FP f (Hunk (line1 + lengthnew2 - lengthold2) old1 new1) :<+ FP f (Hunk line2 old2 new2))+ | otherwise = seq f Nothing+ where lengthnew1 = length new1+ lengthnew2 = length new2+ lengthold1 = length old1+ lengthold2 = length old2+commuteHunk _ _ = impossible+\end{code}+Hunks, of course, can be coalesced if they have any overlap. Note that+coalesce code doesn't check if the two patches are conflicting. If you are+coalescing two conflicting hunks, you've already got a bug somewhere.++\begin{code}+coalesceHunk :: FileName+ -> Int -> [B.ByteString] -> [B.ByteString]+ -> Int -> [B.ByteString] -> [B.ByteString]+ -> Maybe (Prim C(x y))+coalesceHunk f line1 old1 new1 line2 old2 new2+ | line1 == line2 && lengthold1 < lengthnew2 =+ if take lengthold1 new2 /= old1+ then Nothing+ else case drop lengthold1 new2 of+ extranew -> Just (FP f (Hunk line1 old2 (new1 ++ extranew)))+ | line1 == line2 && lengthold1 > lengthnew2 =+ if take lengthnew2 old1 /= new2+ then Nothing+ else case drop lengthnew2 old1 of+ extraold -> Just (FP f (Hunk line1 (old2 ++ extraold) new1))+ | line1 == line2 = if new2 == old1 then Just (FP f (Hunk line1 old2 new1))+ else Nothing+ | line1 < line2 && lengthold1 >= line2 - line1 =+ case take (line2 - line1) old1 of+ extra-> coalesceHunk f line1 old1 new1 line1 (extra ++ old2) (extra ++ new2)+ | line1 > line2 && lengthnew2 >= line1 - line2 =+ case take (line1 - line2) new2 of+ extra-> coalesceHunk f line2 (extra ++ old1) (extra ++ new1) line2 old2 new2+ | otherwise = Nothing+ where lengthold1 = length old1+ lengthnew2 = length new2+\end{code}++One of the most important pieces of code is the canonization of a hunk,+which is where the ``diff'' algorithm is performed. This algorithm begins+with chopping off the identical beginnings and endings of the old and new+hunks. This isn't strictly necessary, but is a good idea, since this+process is $O(n)$, while the primary diff algorithm is something+considerably more painful than that\ldots\ actually the head would be dealt+with all right, but with more space complexity. I think it's more+efficient to just chop the head and tail off first.++\begin{code}+canonizeHunk :: FileName -> Int+ -> [B.ByteString] -> [B.ByteString] -> FL Prim C(x y)+canonizeHunk f line old new+ | null old || null new+ = FP f (Hunk line old new) :>: NilFL+canonizeHunk f line old new = make_holey f line $ getChanges old new++make_holey :: FileName -> Int -> [(Int,[B.ByteString], [B.ByteString])]+ -> FL Prim C(x y)+make_holey f line changes =+ unsafeMap_l2f (\ (l,o,n) -> FP f (Hunk (l+line) o n)) changes+ +try_tok_replace :: String -> String -> String+ -> [B.ByteString] -> Maybe [B.ByteString]+try_tok_replace t o n mss =+ mapM (fmap B.concat . try_tok_internal t (BC.pack o) (BC.pack n)) mss+++try_tok_internal :: String -> B.ByteString -> B.ByteString+ -> B.ByteString -> Maybe [B.ByteString]+try_tok_internal _ o n s | isNothing (substrPS o s) &&+ isNothing (substrPS n s) = Just [s]+try_tok_internal t o n s =+ case BC.break (regChars t) s of+ (before,s') ->+ case BC.break (not . regChars t) s' of+ (tok,after) ->+ case try_tok_internal t o n after of+ Nothing -> Nothing+ Just rest ->+ if tok == o+ then Just $ before : n : rest+ else if tok == n+ then Nothing+ else Just $ before : tok : rest++modernizePrim :: Prim C(x y) -> FL Prim C(x y)+modernizePrim (Split ps) = concatFL $ mapFL_FL modernizePrim ps+modernizePrim p = p :>: NilFL++instance MyEq Prim where+ unsafeCompare (Move a b) (Move c d) = a == c && b == d+ unsafeCompare (DP d1 p1) (DP d2 p2)+ = d1 == d2 && p1 `unsafeCompare` p2 + unsafeCompare (FP f1 fp1) (FP f2 fp2)+ = f1 == f2 && fp1 `unsafeCompare` fp2+ unsafeCompare (Split ps1) (Split ps2)+ = eq_FL unsafeCompare ps1 ps2+ unsafeCompare (ChangePref a1 b1 c1) (ChangePref a2 b2 c2)+ = c1 == c2 && b1 == b2 && a1 == a2+ unsafeCompare Identity Identity = True+ unsafeCompare _ _ = False++merge_orders :: Ordering -> Ordering -> Ordering+merge_orders EQ x = x+merge_orders LT _ = LT+merge_orders GT _ = GT++-- | 'comparePrim' @p1 p2@ is used to provide an arbitrary ordering between+-- @p1@ and @p2@. Basically, identical patches are equal and+-- @Move < DP < FP < Split < Identity < ChangePref@.+-- Everything else is compared in dictionary order of its arguments.+comparePrim :: Prim C(x y) -> Prim C(w z) -> Ordering+comparePrim (Move a b) (Move c d) = compare (a, b) (c, d)+comparePrim (Move _ _) _ = LT+comparePrim _ (Move _ _) = GT+comparePrim (DP d1 p1) (DP d2 p2) = compare (d1, p1) $ unsafeCoerceP (d2, p2)+comparePrim (DP _ _) _ = LT+comparePrim _ (DP _ _) = GT+comparePrim (FP f1 fp1) (FP f2 fp2) = compare (f1, fp1) $ unsafeCoerceP (f2, fp2)+comparePrim (FP _ _) _ = LT+comparePrim _ (FP _ _) = GT+comparePrim (Split ps1) (Split ps2) = compare_FL comparePrim ps1 $ unsafeCoerceP ps2+comparePrim (Split _) _ = LT+comparePrim _ (Split _) = GT+comparePrim Identity Identity = EQ+comparePrim Identity _ = LT+comparePrim _ Identity = GT+comparePrim (ChangePref a1 b1 c1) (ChangePref a2 b2 c2)+ = compare (c1, b1, a1) (c2, b2, a2)++eq_FL :: (FORALL(b c d e) a C(b c) -> a C(d e) -> Bool)+ -> FL a C(x y) -> FL a C(w z) -> Bool+eq_FL _ NilFL NilFL = True+eq_FL f (x:>:xs) (y:>:ys) = f x y && eq_FL f xs ys+eq_FL _ _ _ = False++compare_FL :: (FORALL(b c d e) a C(b c) -> a C(d e) -> Ordering)+ -> FL a C(x y) -> FL a C(w z) -> Ordering+compare_FL _ NilFL NilFL = EQ+compare_FL _ NilFL _ = LT+compare_FL _ _ NilFL = GT+compare_FL f (x:>:xs) (y:>:ys) = f x y `merge_orders` compare_FL f xs ys+ ++class FromPrim p where+ fromPrim :: Prim C(x y) -> p C(x y)++class FromPrim p => ToFromPrim p where+ toPrim :: p C(x y) -> Maybe (Prim C(x y))++class FromPrims p where+ fromPrims :: FL Prim C(x y) -> p C(x y)+ joinPatches :: FL p C(x y) -> p C(x y)++instance FromPrim Prim where+ fromPrim = id+instance ToFromPrim Prim where+ toPrim = Just . id++instance FromPrim p => FromPrims (FL p) where+ fromPrims = mapFL_FL fromPrim+ joinPatches = concatFL+instance FromPrim p => FromPrims (RL p) where+ fromPrims = reverseFL . mapFL_FL fromPrim+ joinPatches = concatRL . reverseFL++class (Invert p, Commute p, Effect p) => Conflict p where+ list_conflicted_files :: p C(x y) -> [FilePath]+ list_conflicted_files p =+ nubsort $ concatMap (unseal list_touched_files) $ concat $ resolve_conflicts p+ resolve_conflicts :: p C(x y) -> [[Sealed (FL Prim C(y))]]+ resolve_conflicts _ = []+ -- | If 'commute_no_conflicts' @x :> y@ succeeds, we know that that @x@ commutes+ -- past @y@ without any conflicts. This function is useful for patch types+ -- for which 'commute' is defined to always succeed; so we need some way to+ -- pick out the specific cases where commutation succeeds without any conflicts.+ --+ -- Consider the commute square with patch names written in capital letters and+ -- repository states written in small letters.+ --+ -- @+ -- X+ -- o-->--a+ -- | |+ -- Y' v v Y+ -- | |+ -- z-->--b+ -- X'+ -- @+ --+ -- The default definition of this function checks that we can mirror the+ -- commutation with patch inverses (written with the negative sign)+ --+ -- @+ -- -X X+ -- a-->--o-->--a+ -- | | |+ -- Y'' v Y' v v Y+ -- | | |+ -- b-->--z-->--b+ -- (-X)' X'+ -- @+ --+ --+ -- We check that commuting @X@ and @Y@ succeeds, as does commuting @-X@ and @Y'@.+ -- It also checks that @Y'' == Y@ and that @-(X')@ is the same as @(-X)'@+ commute_no_conflicts :: (p :> p) C(x y) -> Maybe ((p :> p) C(x y))+ commute_no_conflicts (x:>y) =+ do y':>x' <- commute (x:>y)+ y'':>ix'' <- commute (invert x :> y')+ IsEq <- return $ y'' =\/= y+ IsEq <- return $ ix'' =\/= invert x'+ return (y':>x')+ conflictedEffect :: p C(x y) -> [IsConflictedPrim]+ conflictedEffect x = case list_conflicted_files x of+ [] -> mapFL (IsC Okay) $ effect x+ _ -> mapFL (IsC Conflicted) $ effect x++instance Conflict p => Conflict (FL p) where+ list_conflicted_files = nubsort . concat . mapFL list_conflicted_files+ resolve_conflicts NilFL = []+ resolve_conflicts x = resolve_conflicts $ reverseFL x+ commute_no_conflicts (NilFL :> x) = Just (x :> NilFL)+ commute_no_conflicts (x :> NilFL) = Just (NilFL :> x)+ commute_no_conflicts (xs :> ys) = do ys' :> rxs' <- commute_no_conflictsRLFL (reverseFL xs :> ys)+ return $ ys' :> reverseRL rxs'+ conflictedEffect = concat . mapFL conflictedEffect++instance Conflict p => Conflict (RL p) where+ list_conflicted_files = nubsort . concat . mapRL list_conflicted_files+ resolve_conflicts x = rcs x NilFL+ where rcs :: RL p C(x y) -> FL p C(y w) -> [[Sealed (FL Prim C(w))]]+ rcs NilRL _ = []+ rcs (p:<:ps) passedby | (_:_) <- resolve_conflicts p =+ case commute_no_conflictsFL (p:>passedby) of+ Just (_:> p') -> resolve_conflicts p' ++ rcs ps (p:>:passedby)+ Nothing -> rcs ps (p:>:passedby)+ rcs (p:<:ps) passedby = seq passedby $ rcs ps (p:>:passedby)+ commute_no_conflicts (NilRL :> x) = Just (x :> NilRL)+ commute_no_conflicts (x :> NilRL) = Just (NilRL :> x)+ commute_no_conflicts (xs :> ys) = do ys' :> rxs' <- commute_no_conflictsRLFL (xs :> reverseRL ys)+ return $ reverseFL ys' :> rxs'+ conflictedEffect = concat . reverse . mapRL conflictedEffect++data IsConflictedPrim where+ IsC :: !ConflictState -> !(Prim C(x y)) -> IsConflictedPrim+data ConflictState = Okay | Conflicted | Duplicated deriving ( Eq, Ord, Show, Read)++-- | Patches whose concrete effect which can be expressed as a list of+-- primitive patches.+--+-- A minimal definition would be either of @effect@ or @effectRL@.+class Effect p where+ effect :: p C(x y) -> FL Prim C(x y)+ effect = reverseRL . effectRL+ effectRL :: p C(x y) -> RL Prim C(x y)+ effectRL = reverseFL . effect+ isHunk :: p C(x y) -> Maybe (Prim C(x y))+ isHunk _ = Nothing++instance Effect Prim where+ effect p | IsEq <- sloppyIdentity p = NilFL+ | otherwise = p :>: NilFL+ effectRL p | IsEq <- sloppyIdentity p = NilRL+ | otherwise = p :<: NilRL+ isHunk p = if is_hunk p then Just p else Nothing++instance Conflict Prim++instance Effect p => Effect (FL p) where+ effect p = concatFL $ mapFL_FL effect p+ effectRL p = concatRL $ mapRL_RL effectRL $ reverseFL p++instance Effect p => Effect (RL p) where+ effect p = concatFL $ mapFL_FL effect $ reverseRL p+ effectRL p = concatRL $ mapRL_RL effectRL p++commute_no_conflictsFL :: Conflict p => (p :> FL p) C(x y) -> Maybe ((FL p :> p) C(x y))+commute_no_conflictsFL (p :> NilFL) = Just (NilFL :> p)+commute_no_conflictsFL (q :> p :>: ps) = do p' :> q' <- commute_no_conflicts (q :> p)+ ps' :> q'' <- commute_no_conflictsFL (q' :> ps)+ return (p' :>: ps' :> q'')++commute_no_conflictsRL :: Conflict p => (RL p :> p) C(x y) -> Maybe ((p :> RL p) C(x y))+commute_no_conflictsRL (NilRL :> p) = Just (p :> NilRL)+commute_no_conflictsRL (p :<: ps :> q) = do q' :> p' <- commute_no_conflicts (p :> q)+ q'' :> ps' <- commute_no_conflictsRL (ps :> q')+ return (q'' :> p' :<: ps')++commute_no_conflictsRLFL :: Conflict p => (RL p :> FL p) C(x y) -> Maybe ((FL p :> RL p) C(x y))+commute_no_conflictsRLFL (NilRL :> ys) = Just (ys :> NilRL)+commute_no_conflictsRLFL (xs :> NilFL) = Just (NilFL :> xs)+commute_no_conflictsRLFL (xs :> y :>: ys) = do y' :> xs' <- commute_no_conflictsRL (xs :> y)+ ys' :> xs'' <- commute_no_conflictsRLFL (xs' :> ys)+ return (y' :>: ys' :> xs'')++\end{code}
@@ -0,0 +1,357 @@+% Copyright (C) 2007 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\documentclass{article}+%\usepackage{color}++\usepackage{verbatim}+\usepackage{html}+\usepackage{fancyvrb}+\newenvironment{code}{\comment}{\endcomment}+% \newenvironment{code}{\color{blue}\verbatim}{\endverbatim}++\newcommand{\commutes}{\longleftrightarrow}++\begin{document}++\begin{code}+{-# LANGUAGE CPP #-}+{-# OPTIONS_GHC -cpp -fno-warn-deprecations -fno-warn-orphans -fglasgow-exts #-}++#include "gadts.h"++module Darcs.Patch.Properties ( recommute, commute_inverses, permutivity, partial_permutivity,+ identity_commutes, inverse_doesnt_commute,+ patch_and_inverse_commute, merge_either_way,+ show_read,+ merge_commute, merge_consistent, merge_arguments_consistent,+ join_inverses, join_commute ) where++import Control.Monad ( msum, mplus )+import Darcs.Show ( Show2(..) )+import Darcs.Patch.Patchy+import Darcs.Patch.Prim+import Darcs.Patch ()+import Darcs.Patch.Read ( readPatch )+import Darcs.Ordered+import Darcs.Sealed ( Sealed(Sealed) )+import Printer ( Doc, renderPS, redText, greenText, ($$) )+--import Darcs.ColorPrinter ( traceDoc )+\end{code}++\section{Patch properties}++Darcs is built on a hierarchy of patch types. At the lowest level are+``primitive'' patches, and from these building blocks, a whole hierarchy of+patch types are built. Each of these patch types must support a number of+functions, which must obey a number of laws.++\subsection{Properties of identity}++\newtheorem{prp}{Property}++\begin{prp}[Identity commutes trivially]+ The identity patch must commute with any patch without modifying said patch.+\end{prp}++\begin{code}+identity_commutes :: forall p C(x y). Patchy p => p C(x y) -> Maybe Doc+identity_commutes p = case commute (p :> identity) of+ Nothing -> Just $ redText "identity_commutes failed:" $$ showPatch p+ Just (i :> p') | IsEq <- i =\/= identity,+ IsEq <- p' =\/= p ->+ checkRightIdentity $ commute $ identity :> p+ Just _ -> Just $ greenText "identity_commutes"+ where checkRightIdentity :: Maybe ((p :> p) C(x y)) -> Maybe Doc+ checkRightIdentity Nothing = Just $ redText "identity_commutes failed 2:" $$ showPatch p+ checkRightIdentity (Just (p2 :> i2)) | IsEq <- i2 =\/= identity,+ IsEq <- p2 =\/= p = Nothing+ checkRightIdentity (Just _) = Just $ greenText "identity_commutes 2"+\end{code}++\begin{prp}[Inverse doesn't commute]+ A patch and its inverse will always commute, unless that patch is an+ identity patch (or an identity-like patch that has no effect).+\end{prp}++\begin{code}+inverse_doesnt_commute :: Patchy p => p C(a b) -> Maybe Doc+inverse_doesnt_commute p | IsEq <- sloppyIdentity p = Nothing+ | otherwise = do p' :> _ <- commute (invert p :> p)+ Just $ redText "inverse_doesnt_commute" $$ showPatch p'+\end{code}++\subsection{Commute properties}++\begin{prp}[Recommute]+ $AB \commutes B'A'$ if and only if $B'A' \commutes AB$+\end{prp}++\begin{code}+recommute :: Patchy p => (FORALL(x y) ((p :> p) C(x y) -> Maybe ((p :> p) C(x y))))+ -> (p :> p) C(a b) -> Maybe Doc+recommute c (x :> y) =+ case c (x :> y) of+ Nothing -> Nothing+ Just (y' :> x') ->+ case c (y' :> x') of+ Nothing -> Just (redText "failed" $$ showPatch y' $$ redText ":>" $$ showPatch x')+ Just (x'' :> y'') ->+ case y'' =/\= y of+ NotEq -> Just (redText "y'' =/\\= y failed, where x" $$ showPatch x $$+ redText ":> y" $$ showPatch y $$+ redText "y'" $$ showPatch y' $$+ redText ":> x'" $$ showPatch x' $$+ redText "x''" $$ showPatch x'' $$+ redText ":> y''" $$ showPatch y'')+ IsEq -> case x'' =/\= x of+ NotEq -> Just (redText "x'' /= x" $$ showPatch x'' $$ redText ":>" $$ showPatch y'')+ IsEq -> Nothing+\end{code}++\begin{prp}[Commute inverses]+ $AB \commutes B'A'$ if and only if $B^{-1}A^{-1} \commutes A'^{-1}B'^{-1}$+\end{prp}++\begin{code}+commute_inverses :: Patchy p => (FORALL(x y) (p :> p) C(x y) -> Maybe ((p :> p) C(x y)))+ -> (p :> p) C(a b) -> Maybe Doc+commute_inverses c (x :> y) =+ case c (x :> y) of+ Nothing -> Nothing+ Just (y' :> x') ->+ case c (invert y :> invert x) of+ Nothing -> Just $ redText "second commute failed" $$+ redText "x" $$ showPatch x $$ redText "y" $$ showPatch y $$+ redText "y'" $$ showPatch y' $$ redText "x'" $$ showPatch x'+ Just (ix' :> iy') ->+ case invert ix' =/\= x' of+ NotEq -> Just $ redText "invert ix' /= x'" $$+ redText "x" $$ showPatch x $$+ redText "y" $$ showPatch y $$+ redText "y'" $$ showPatch y' $$+ redText "x'" $$ showPatch x' $$+ redText "ix'" $$ showPatch ix' $$+ redText "iy'" $$ showPatch iy' $$+ redText "invert ix'" $$ showPatch (invert ix') $$+ redText "invert iy'" $$ showPatch (invert iy')+ IsEq -> case y' =\/= invert iy' of+ NotEq -> Just $ redText "y' /= invert iy'" $$ showPatch iy' $$ showPatch y'+ IsEq -> Nothing+\end{code}++\begin{prp}[Patch and inverse]+ If $AB \commutes B'A'$ then $A^{-1}B' \commutes BA'^{-1}$+\end{prp}++This property is only true of primitive patches.++\begin{code}+patch_and_inverse_commute :: Patchy p =>+ (FORALL(x y) (p :> p) C(x y) -> Maybe ((p :> p) C(x y)))+ -> (p :> p) C(a b) -> Maybe Doc+patch_and_inverse_commute c (x :> y) =+ do y' :> x' <- c (x :> y)+ case c (invert x :> y') of+ Nothing -> Just (redText "failure in patch_and_inverse_commute")+ Just (y'' :> ix') ->+ case y'' =\/= y of+ NotEq -> Just (redText "y'' /= y" $$+ redText "x" $$ showPatch x $$+ redText "y" $$ showPatch y $$+ redText "x'" $$ showPatch x' $$+ redText "y'" $$ showPatch y' $$+ redText "y''" $$ showPatch y'' $$+ redText ":> x'" $$ showPatch x')+ IsEq -> case x' =\/= invert ix' of+ NotEq -> Just (redText "x' /= invert ix'" $$+ redText "y''" $$ showPatch y'' $$+ redText ":> x'" $$ showPatch x' $$+ redText "invert x" $$ showPatch (invert x) $$+ redText ":> y" $$ showPatch y $$+ redText "y'" $$ showPatch y' $$+ redText "ix'" $$ showPatch ix')+ IsEq -> Nothing+\end{code}++\begin{prp}[Permutivity]+ (to be added...)+\end{prp}++\begin{code}+permutivity :: Patchy p => (FORALL(x y) (p :> p) C(x y) -> Maybe ((p :> p) C(x y)))+ -> (p :> p :> p) C(a b) -> Maybe Doc+permutivity c (x:>y:>z) =+ do y1 :> x1 <- c (x :> y)+ z2 :> y2 <- --traceDoc (greenText "first commuted") $+ c (y :> z)+ z3 :> x3 <- --traceDoc (greenText "second commuted") $+ c (x :> z2)+ case c (x1 :> z) of+ Nothing -> Just $ redText "permutivity1"+ Just (z4 :> x4) ->+ --traceDoc (greenText "third commuted" $$+ -- greenText "about to commute" $$+ -- greenText "y1" $$ showPatch y1 $$+ -- greenText "z4" $$ showPatch z4) $+ case c (y1 :> z4) of+ Nothing -> Just $ redText "permutivity2"+ Just (z3_ :> y4)+ | IsEq <- z3_ =\/= z3 ->+ --traceDoc (greenText "passed z3") $ error "foobar test" $+ case c (y4 :> x4) of+ Nothing -> Just $ redText "permutivity5: input was" $$+ redText "x" $$ showPatch x $$+ redText "y" $$ showPatch y $$+ redText "z" $$ showPatch z $$+ redText "z3" $$ showPatch z3 $$+ redText "failed commute of" $$+ redText "y4" $$ showPatch y4 $$+ redText "x4" $$ showPatch x4 $$+ redText "whereas commute of x and y give" $$+ redText "y1" $$ showPatch y1 $$+ redText "x1" $$ showPatch x1+ Just (x3_ :> y2_)+ | NotEq <- x3_ =\/= x3 -> Just $ redText "permutivity6"+ | NotEq <- y2_ =/\= y2 -> Just $ redText "permutivity7"+ | otherwise -> Nothing+ | otherwise ->+ Just $ redText "permutivity failed" $$+ redText "z3" $$ showPatch z3 $$+ redText "z3_" $$ showPatch z3_++partial_permutivity :: Patchy p => (FORALL(x y) (p :> p) C(x y) -> Maybe ((p :> p) C(x y)))+ -> (p :> p :> p) C(a b) -> Maybe Doc+partial_permutivity c (xx:>yy:>zz) = pp (xx:>yy:>zz) `mplus` pp (invert zz:>invert yy:>invert xx)+ where pp (x:>y:>z) = do z1 :> y1 <- c (y :> z)+ _ :> x1 <- c (x :> z1)+ case c (x :> y) of+ Just _ -> Nothing -- this is covered by full permutivity test above+ Nothing ->+ case c (x1 :> y1) of+ Nothing -> Nothing+ Just _ -> Just $ greenText "partial_permutivity error" $$+ greenText "x" $$ showPatch x $$+ greenText "y" $$ showPatch y $$+ greenText "z" $$ showPatch z++merge_arguments_consistent :: Patchy p =>+ (FORALL(x y) p C(x y) -> Maybe Doc)+ -> (p :\/: p) C(a b) -> Maybe Doc+merge_arguments_consistent is_consistent (x :\/: y) =+ msum [(\z -> redText "merge_arguments_consistent x" $$ showPatch x $$ z) `fmap` is_consistent x,+ (\z -> redText "merge_arguments_consistent y" $$ showPatch y $$ z) `fmap` is_consistent y]++merge_consistent :: Patchy p =>+ (FORALL(x y) p C(x y) -> Maybe Doc)+ -> (p :\/: p) C(a b) -> Maybe Doc+merge_consistent is_consistent (x :\/: y) =+ case merge (x :\/: y) of+ y' :/\: x' ->+ msum [(\z -> redText "merge_consistent x" $$ showPatch x $$ z) `fmap` is_consistent x,+ (\z -> redText "merge_consistent y" $$ showPatch y $$ z) `fmap` is_consistent y,+ (\z -> redText "merge_consistent x'" $$ showPatch x' $$ z $$+ redText "where x' comes from x" $$ showPatch x $$+ redText "and y" $$ showPatch y) `fmap` is_consistent x',+ (\z -> redText "merge_consistent y'" $$ showPatch y' $$ z) `fmap` is_consistent y']++merge_either_way :: Patchy p => (p :\/: p) C(x y) -> Maybe Doc+merge_either_way (x :\/: y) =+ case merge (x :\/: y) of+ y' :/\: x' -> case merge (y :\/: x) of+ x'' :/\: y'' | IsEq <- x'' =\/= x',+ IsEq <- y'' =\/= y' -> Nothing+ | otherwise -> Just $ redText "merge_either_way bug"++merge_commute :: Patchy p => (p :\/: p) C(x y) -> Maybe Doc+merge_commute (x :\/: y) =+ case merge (x :\/: y) of+ y' :/\: x' ->+ case commute (x :> y') of+ Nothing -> Just $ redText "merge_commute 1" $$+ redText "x" $$ showPatch x $$+ redText "y" $$ showPatch y $$+ redText "x'" $$ showPatch x' $$+ redText "y'" $$ showPatch y'+ Just (y_ :> x'_)+ | IsEq <- y_ =\/= y,+ IsEq <- x'_ =\/= x' ->+ case commute (y :> x') of+ Nothing -> Just $ redText "merge_commute 2 failed" $$+ redText "x" $$ showPatch x $$+ redText "y" $$ showPatch y $$+ redText "x'" $$ showPatch x' $$+ redText "y'" $$ showPatch y'+ Just (x_ :> y'_)+ | IsEq <- x_ =\/= x,+ IsEq <- y'_ =\/= y' -> Nothing+ | otherwise -> Just $ redText "merge_commute 3" $$+ redText "x" $$ showPatch x $$+ redText "y" $$ showPatch y $$+ redText "x'" $$ showPatch x' $$+ redText "y'" $$ showPatch y' $$+ redText "x_" $$ showPatch x_ $$+ redText "y'_" $$ showPatch y'_+ | otherwise -> Just $ redText "merge_commute 4" $$+ redText "x" $$ showPatch x $$+ redText "y" $$ showPatch y $$+ redText "x'" $$ showPatch x' $$+ redText "y'" $$ showPatch y' $$+ redText "x'_" $$ showPatch x'_ $$+ redText "y_" $$ showPatch y_++join_inverses :: (FORALL(x y) (Prim :> Prim) C(x y) -> Maybe (Prim C(x y)))+ -> Prim C(a b) -> Maybe Doc+join_inverses j p = case j (invert p :> p) of+ Just Identity -> Nothing+ Just p' -> Just $ redText "join_inverses gave just" $$ showPatch p'+ Nothing -> Just $ redText "join_inverses failed"++join_commute :: (FORALL(x y) (Prim :> Prim) C(x y) -> Maybe (Prim C(x y)))+ -> (Prim :> Prim :> Prim) C(a b) -> Maybe Doc+join_commute j (a :> b :> c) =+ do x <- j (b :> c)+ case commuteFL (a :> b :>: c :>: NilFL) of+ Right (b' :>: c' :>: NilFL :> a') ->+ case commute (a :> x) of+ Nothing -> Just $ greenText "join_commute 1"+ Just (x' :> a'') ->+ case a'' =/\= a' of+ NotEq -> Just $ greenText "join_commute 3"+ IsEq -> case j (b' :> c') of+ Nothing -> Just $ greenText "join_commute 4"+ Just x'' -> case x' =\/= x'' of+ NotEq -> Just $ greenText "join_commute 5"+ IsEq -> Nothing+ _ -> Nothing++show_read :: (Show2 p, Patchy p) => p C(a b) -> Maybe Doc+show_read p = let ps = renderPS (showPatch p)+ in case readPatch ps of+ Nothing -> Just (redText "unable to read " $$ showPatch p)+ Just (Sealed p',_) | IsEq <- p' =\/= p -> Nothing+ | otherwise -> Just $ redText "trouble reading patch p" $$+ showPatch p $$+ redText "reads as p'" $$+ showPatch p' $$+ redText "aka" $$+ greenText (show2 p) $$+ redText "and" $$+ greenText (show2 p')+\end{code}++\end{document}
@@ -0,0 +1,255 @@+-- Copyright (C) 2002-2003 David Roundy+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.++{-# OPTIONS_GHC -cpp -fno-warn-orphans #-}+{-# LANGUAGE CPP #-}++module Darcs.Patch.Read ( readPrim, readPatch )+ where++import Prelude hiding ( pi )+import Control.Monad ( liftM )++#include "gadts.h"++import ByteStringUtils ( breakFirstPS, fromHex2PS, readIntPS, dropSpace )+import qualified Data.ByteString.Char8 as BC (head, unpack, dropWhile, break)+import qualified Data.ByteString as B (ByteString, null, init, tail, empty, concat)++import Darcs.Patch.FileName ( FileName, fn2fp, fp2fn, ps2fn, decode_white )+import Darcs.Patch.Core ( Patch(..), Named(..) )+import Darcs.Patch.Prim ( Prim(..), FileNameFormat(..),+ DirPatchType(..), FilePatchType(..),+ hunk, binary )+#ifndef GADT_WITNESSES+import Darcs.Patch.Commute ( merger )+import Darcs.Patch.Patchy ( invert )+#endif+import Darcs.Patch.Info ( PatchInfo, readPatchInfo )+import Darcs.Patch.ReadMonads (ParserM, work, maybe_work, alter_input,+ parse_strictly, peek_input, lex_string, lex_eof, my_lex)+#include "impossible.h"+import Darcs.Patch.Patchy ( ReadPatch, readPatch', bracketedFL )+import Darcs.Ordered ( FL(..) )+import Darcs.Sealed ( Sealed(..), seal, mapSeal )++readPatch :: ReadPatch p => B.ByteString -> Maybe (Sealed (p C(x )), B.ByteString)+readPatch ps = case parse_strictly (readPatch' False) ps of+ Just (Just p, ps') -> Just (p, ps')+ _ -> Nothing++instance ReadPatch p => ReadPatch (Named p) where+ readPatch' want_eof+ = do s <- peek_input+ case liftM (BC.unpack . fst) $ my_lex s of+ Just ('[':_) -> liftM Just $ readNamed want_eof -- ]+ _ -> return Nothing++instance ReadPatch Prim where+ readPatch' w = readPrim OldFormat w++readPrim :: ParserM m => FileNameFormat -> Bool -> m (Maybe (Sealed (Prim C(x ))))+readPrim x _+ = do s <- peek_input+ case liftM (BC.unpack . fst) $ my_lex s of+ Just "{}" -> do work my_lex+ return $ Just $ seal Identity+ Just "(" -> liftM Just $ readSplit x -- )+ Just "hunk" -> liftM (Just . seal) $ readHunk x+ Just "replace" -> liftM (Just . seal) $ readTok x+ Just "binary" -> liftM (Just . seal) $ readBinary x+ Just "addfile" -> liftM (Just . seal) $ readAddFile x+ Just "adddir" -> liftM (Just . seal) $ readAddDir x+ Just "rmfile" -> liftM (Just . seal) $ readRmFile x+ Just "rmdir" -> liftM (Just . seal) $ readRmDir x+ Just "move" -> liftM (Just . seal) $ readMove x+ Just "changepref" -> liftM (Just . seal) $ readChangePref+ _ -> return Nothing++instance ReadPatch Patch where+ readPatch' want_eof+ = do mps <- bracketedFL (fromIntegral $ fromEnum '{') (fromIntegral $ fromEnum '}')+ case mps of+ Just (Sealed ps) -> return $ Just $ Sealed $ ComP ps+ Nothing -> do s <- peek_input+ case liftM (BC.unpack . fst) $ my_lex s of+#ifndef GADT_WITNESSES+ Just "merger" -> liftM (Just . seal) $ readMerger True+ Just "regrem" -> liftM (Just . seal) $ readMerger False+#endif+ _ -> liftM (fmap (mapSeal PP)) $ readPatch' want_eof++read_patches :: ParserM m => FileNameFormat -> String -> Bool -> m (Sealed (FL Prim C(x )))+read_patches x str want_eof+ = do mp <- readPrim x False+ case mp of+ Nothing -> do unit <- lex_string str+ case unit of+ () -> if want_eof then do unit' <- lex_eof+ case unit' of+ () -> return $ seal NilFL+ else return $ seal NilFL+ Just (Sealed p) -> do Sealed ps <- read_patches x str want_eof+ return $ seal (p:>:ps)++readSplit :: ParserM m => FileNameFormat -> m (Sealed (Prim C(x )))+readSplit x = do+ work my_lex+ ps <- read_patches x ")" False+ return $ Split `mapSeal` ps++readFileName :: FileNameFormat -> B.ByteString -> FileName+readFileName OldFormat = ps2fn+readFileName NewFormat = fp2fn . decode_white . BC.unpack++readHunk :: ParserM m => FileNameFormat -> m (Prim C(x y))+readHunk x = do+ work my_lex+ fi <- work my_lex+ l <- work readIntPS+ have_nl <- skip_newline+ if have_nl+ then do work $ lines_starting_with ' ' -- skipping context+ old <- work $ lines_starting_with '-'+ new <- work $ lines_starting_with '+'+ work $ lines_starting_with ' ' -- skipping context+ return $ hunk (fn2fp $ readFileName x fi) l old new+ else return $ hunk (fn2fp $ readFileName x fi) l [] []++skip_newline :: ParserM m => m Bool+skip_newline = do s <- peek_input+ if B.null s+ then return False+ else if BC.head s /= '\n'+ then return False+ else alter_input B.tail >> return True++readTok :: ParserM m => FileNameFormat -> m (Prim C(x y))+readTok x = do+ work my_lex+ f <- work my_lex+ regstr <- work my_lex+ o <- work my_lex+ n <- work my_lex+ return $ FP (readFileName x f) $ TokReplace (BC.unpack (drop_brackets regstr))+ (BC.unpack o) (BC.unpack n)+ where drop_brackets = B.init . B.tail+++-- * Binary file modification+--+-- | Modify a binary file+--+-- > binary FILENAME+-- > oldhex+-- > *HEXHEXHEX+-- > ...+-- > newhex+-- > *HEXHEXHEX+-- > ...+readBinary :: ParserM m => FileNameFormat -> m (Prim C(x y))+readBinary x = do+ work my_lex+ fi <- work my_lex+ work my_lex+ alter_input dropSpace+ old <- work $ lines_starting_with '*'+ work my_lex+ alter_input dropSpace+ new <- work $ lines_starting_with '*'+ return $ binary (fn2fp $ readFileName x fi)+ (fromHex2PS $ B.concat old)+ (fromHex2PS $ B.concat new)++readAddFile :: ParserM m => FileNameFormat -> m (Prim C(x y))+readAddFile x = do work my_lex+ f <- work my_lex+ return $ FP (readFileName x f) AddFile++readRmFile :: ParserM m => FileNameFormat -> m (Prim C(x y))+readRmFile x = do work my_lex+ f <- work my_lex+ return $ FP (readFileName x f) RmFile++readMove :: ParserM m => FileNameFormat -> m (Prim C(x y))+readMove x = do work my_lex+ d <- work my_lex+ d' <- work my_lex+ return $ Move (readFileName x d) (readFileName x d')++readChangePref :: ParserM m => m (Prim C(x y))+readChangePref+ = do work my_lex+ p <- work my_lex+ f <- work (Just . BC.break ((==)'\n') . B.tail . BC.dropWhile (== ' '))+ t <- work (Just . BC.break ((==)'\n') . B.tail)+ return $ ChangePref (BC.unpack p) (BC.unpack f) (BC.unpack t)++readAddDir :: ParserM m => FileNameFormat -> m (Prim C(x y))+readAddDir x = do work my_lex+ f <- work my_lex+ return $ DP (readFileName x f) AddDir++readRmDir :: ParserM m => FileNameFormat -> m (Prim C(x y))+readRmDir x = do work my_lex+ f <- work my_lex+ return $ DP (readFileName x f) RmDir++#ifndef GADT_WITNESSES+readMerger :: ParserM m => Bool -> m (Patch C(x y))+readMerger b = do work my_lex+ g <- work my_lex+ lex_string "("+ Just (Sealed p1) <- readPatch' False+ Just (Sealed p2) <- readPatch' False+ lex_string ")"+ let m = merger (BC.unpack g) p1 p2+ return $ if b then m else invert m+#endif++readNamed :: (ReadPatch p, ParserM m) => Bool -> m (Sealed (Named p C(x )))+readNamed want_eof+ = do mn <- maybe_work readPatchInfo+ case mn of+ Nothing -> bug "readNamed 1"+ Just n ->+ do d <- read_depends+ Just p <- readPatch' want_eof+ return $ (NamedP n d) `mapSeal` p+read_depends :: ParserM m => m [PatchInfo]+read_depends = do s <- peek_input+ case my_lex s of+ Just (xs, _) | BC.unpack xs == "<" ->+ do work my_lex+ read_pis+ _ -> return []+read_pis :: ParserM m => m [PatchInfo]+read_pis = do mpi <- maybe_work readPatchInfo+ case mpi of+ Just pi -> do pis <- read_pis+ return (pi:pis)+ Nothing -> do alter_input (B.tail . BC.dropWhile (/= '>'))+ return []++lines_starting_with :: Char -> B.ByteString -> Maybe ([B.ByteString], B.ByteString)+lines_starting_with c thes =+ Just (lsw [] thes)+ where lsw acc s | B.null s || BC.head s /= c = (reverse acc, s)+ lsw acc s = let s' = B.tail s+ in case breakFirstPS '\n' s' of+ Just (l, r) -> lsw (l:acc) r+ Nothing -> (reverse (s':acc), B.empty)
@@ -0,0 +1,91 @@++module Darcs.Patch.ReadMonads (ParserM, work, maybe_work, alter_input,+ parse_strictly, parse_lazily,+ peek_input,+ lex_char, lex_string, lex_strings, lex_eof,+ my_lex) where++import ByteStringUtils ( dropSpace, breakSpace )+import qualified Data.ByteString as B (null, empty, ByteString)+import qualified Data.ByteString.Char8 as BC (unpack, pack)++lex_char :: ParserM m => Char -> m ()+lex_char c = lex_string [c]++lex_string :: ParserM m => String -> m ()+lex_string str = work+ $ \s -> case my_lex s of+ Just (xs, ys) | xs == BC.pack str -> Just ((), ys)+ _ -> Nothing++lex_eof :: ParserM m => m ()+lex_eof = work+ $ \s -> if B.null (dropSpace s)+ then Just ((), B.empty)+ else Nothing++lex_strings :: ParserM m => [String] -> m String+lex_strings str =+ work $ \s ->+ case my_lex s of+ Just (xs, ys) | xs' `elem` str -> Just (xs', ys)+ where xs' = BC.unpack xs+ _ -> Nothing++my_lex :: B.ByteString -> Maybe (B.ByteString, B.ByteString)+my_lex s = let s' = dropSpace s+ in if B.null s'+ then Nothing+ else Just $ breakSpace s'++alter_input :: ParserM m+ => (B.ByteString -> B.ByteString) -> m ()+alter_input f = work (\s -> Just ((), f s))++class Monad m => ParserM m where+ work :: (B.ByteString -> Maybe (a, B.ByteString)) -> m a+ maybe_work :: (B.ByteString -> Maybe (a, B.ByteString)) -> m (Maybe a)+ peek_input :: m B.ByteString++----- Strict Monad -----+parse_strictly :: SM a -> B.ByteString -> Maybe (a, B.ByteString)+parse_strictly (SM f) s = f s++newtype SM a = SM (B.ByteString -> Maybe (a, B.ByteString))+instance Monad SM where+ SM m >>= k = SM $ \s -> case m s of+ Nothing -> Nothing+ Just (x, s') ->+ case k x of+ SM y -> y s'+ return x = SM (\s -> Just (x,s))+ fail _ = SM (\_ -> Nothing)++instance ParserM SM where+ work f = SM f+ maybe_work f = SM $ \s -> case f s of+ Just (x, s') -> Just (Just x, s')+ Nothing -> Just (Nothing, s)+ peek_input = SM $ \s -> Just (s, s)++----- Lazy Monad -----+parse_lazily :: LM a -> B.ByteString -> (a, B.ByteString)+parse_lazily (LM f) s = f s++newtype LM a = LM (B.ByteString -> (a, B.ByteString))+instance Monad LM where+ LM m >>= k = LM $ \s -> let (x, s') = m s+ LM y = k x+ in y s'+ return x = LM (\s -> (x,s))+ fail s = error s++instance ParserM LM where+ work f = LM $ \s -> case f s of+ Nothing -> error "parser error"+ Just x -> x+ maybe_work f = LM $ \s -> case f s of+ Nothing -> (Nothing, s)+ Just (x, s') -> (Just x, s')+ peek_input = LM $ \s -> (s, s)+
@@ -0,0 +1,777 @@+-- Copyright (C) 2007 David Roundy+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.++{-# OPTIONS_GHC -cpp -fglasgow-exts -fno-warn-orphans #-}+{-# LANGUAGE CPP #-}+-- , TypeOperators, GADTs, PatternGuards #-}++#include "gadts.h"++-- | Conflictor patches+module Darcs.Patch.Real+ ( RealPatch(..), prim2real, is_consistent, is_forward, is_duplicate,+ pullCommon, mergeUnravelled ) where++import Control.Monad ( mplus, liftM )+import Data.List ( partition, nub )+import Darcs.Patch.Prim ( Prim, FromPrim(..), ToFromPrim(..), Conflict(..), Effect(..),+ showPrim, FileNameFormat(NewFormat),+ IsConflictedPrim(..), ConflictState(..) )+import Darcs.Patch.Read ( readPrim )+import Darcs.Patch.Patchy+import Darcs.Ordered+--import Darcs.Patch.Read ()+--import Darcs.Patch.Viewing ()+--import Darcs.Patch.Apply ()+import Darcs.Patch.Commute ( mangle_unravelled )+import Darcs.Patch.Non ( Non(..), Nonable(..), unNon,+ showNons, showNon, readNons, readNon,+ add, addP, addPs, remP, remPs, remNons,+ (*>), (>*), (*>>), (>>*) )+import Darcs.Patch.Permutations ( commuteWhatWeCanFL, commuteWhatWeCanRL,+ genCommuteWhatWeCanRL,+ removeRL, removeFL, remove_subsequenceFL )+import qualified Data.ByteString.Char8 as BC ( unpack )+import Darcs.Patch.ReadMonads (work, peek_input, my_lex )+import Darcs.Utils ( nubsort )+import Darcs.Sealed ( FlippedSeal(..), Sealed(Sealed), mapSeal )+import Darcs.Show+import Printer ( Doc, renderString, blueText, redText, (<+>), ($$) )+import Darcs.ColorPrinter ( errorDoc, assertDoc )+--import Printer ( greenText )+--import Darcs.ColorPrinter ( traceDoc )+#include "impossible.h"++-- |+-- @Duplicate x@: This patch has no effect since @x@ is already present in the repository+--+-- @Etacilpud x: invert (Duplicate x)@+--+-- @Normal prim@: A primitive patch+--+-- @Conflictor ix xx x@:+-- @ix@ is the set of patches:+--+-- * that conflict with @x@ and also conflict with another patch in the repository+--+-- * that conflict with a patch that conflict with @x@+--+-- @xx@ is the sequence of patches that conflict *only* with @x@+--+-- @x@ is the current patch+--+-- @ix@ and @x@ are stored as @Non@ objects, which include any necessary+-- context to uniquely define the patch that is referred to.+--+-- @InvConflictor ix xx x@: like @invert (Conflictor ix xx x)@+data RealPatch C(x y) where+ Duplicate :: Non RealPatch C(x) -> RealPatch C(x x)+ Etacilpud :: Non RealPatch C(x) -> RealPatch C(x x)+ Normal :: Prim C(x y) -> RealPatch C(x y)+ Conflictor :: [Non RealPatch C(x)] -> FL Prim C(x y) -> Non RealPatch C(x) -> RealPatch C(y x)+ InvConflictor :: [Non RealPatch C(x)] -> FL Prim C(x y) -> Non RealPatch C(x) -> RealPatch C(x y)++-- | 'is_duplicate' @p@ is ' @True@ if @p@ is either a 'Duplicate' or 'Etacilpud' patch+is_duplicate :: RealPatch C(s y) -> Bool+is_duplicate (Duplicate _) = True+is_duplicate (Etacilpud _) = True+is_duplicate _ = False++-- | This is only used for unit testing+is_forward :: RealPatch C(s y) -> Maybe Doc+is_forward p@(InvConflictor _ _ _) =+ Just $ redText "An inverse conflictor" $$ showPatch p+is_forward p@(Etacilpud _) =+ Just $ redText "An inverse duplicate" $$ showPatch p+is_forward _ = Nothing++mergeUnravelled :: [Sealed ((FL Prim) C(x))] -> Maybe (FlippedSeal RealPatch C(x))+mergeUnravelled [] = Nothing+mergeUnravelled [_] = Nothing+mergeUnravelled ws = case mergeUnravelled_private ws of+ Nothing -> Nothing+ Just NilRL -> bug "found no patches in mergeUnravelled"+ Just (z:<:_) -> Just $ FlippedSeal z+ where notNullS :: Sealed ((FL Prim) C(x)) -> Bool+ notNullS (Sealed NilFL) = False+ notNullS _ = True+ mergeUnravelled_private :: [Sealed (FL Prim C(x))] -> Maybe (RL RealPatch C(x x))+ mergeUnravelled_private xs = reverseFL `fmap` mergeConflictingNons+ (map sealed2non $ filter notNullS xs)++-- | 'sealed2non' @(Sealed xs)@ converts @xs@ to a 'Non'.+-- @xs@ must be non-empty since we split this list at the last patch+sealed2non :: Sealed ((FL Prim) C(x)) -> Non RealPatch C(x)+sealed2non (Sealed xs) = case reverseFL xs of+ y:<:ys -> Non (mapFL_FL fromPrim $ reverseRL ys) y+ NilRL -> bug "NilFL encountered in sealed2non"++mergeConflictingNons :: [Non RealPatch C(x)] -> Maybe (FL RealPatch C(x x))+mergeConflictingNons ns = mcn $ map unNon ns+ where mcn :: [Sealed (FL RealPatch C(x))] -> Maybe (FL RealPatch C(x x))+ mcn [] = Just NilFL+ mcn [Sealed p] = case join_effects p of -- this is just a safety check, and could+ NilFL -> Just p -- be removed when we're sure of the code.+ _ -> Nothing+ mcn (Sealed p1:Sealed p2:zs) = case pullCommon p1 p2 of+ Common c ps qs ->+ case merge (ps :\/: qs) of+ qs' :/\: _ -> mcn (Sealed (c +>+ ps +>+ qs'):zs)++join_effects :: Effect p => p C(x y) -> FL Prim C(x y)+join_effects = join_inverses . effect+ where join_inverses :: FL Prim C(x y) -> FL Prim C(x y)+ join_inverses NilFL = NilFL+ join_inverses (p:>:ps) = case removeFL (invert p) ps' of+ Just ps'' -> ps''+ Nothing -> p :>: ps'+ where ps' = join_inverses ps++assertConsistent :: RealPatch C(x y) -> RealPatch C(x y)+assertConsistent x = assertDoc (do e <- is_consistent x+ Just (redText "Inconsistent patch:" $$ showPatch x $$ e)) x++-- | @mergeAfterConflicting@ takes as input a sequence of conflicting+-- patches @xxx@ (which therefore have no effect) and a sequence of+-- primitive patches @yyy@ that follow said sequence of conflicting+-- patches, and may depend upon some of the conflicting patches (as a+-- resolution).++-- The output is two sequences of patches the first consisting of a+-- set of mutually-conflicting patches, and the second having the same+-- effect as the original primitive patch sequence in the input.++-- So far as I can tell, the second output is always identical to+-- @mapFL Normal yyy@++-- The first output is the set of patches from @xxx@ that are depended+-- upon by @yyy@.++mergeAfterConflicting :: FL RealPatch C(x x) -> FL Prim C(x y)+ -> Maybe (FL RealPatch C(x x), FL RealPatch C(x y))+mergeAfterConflicting xxx yyy = --traceDoc (greenText "mergeAfterConflicting xxx" $$ showPatch xxx $$+ -- greenText "and yyy" $$ showPatch yyy) $+ mac (reverseFL xxx) yyy NilFL+ where mac :: RL RealPatch C(x y) -> FL Prim C(y z) -> FL RealPatch C(z a)+ -> Maybe (FL RealPatch C(x x), FL RealPatch C(x a))+ mac NilRL xs goneby = case join_effects goneby of+ NilFL -> Just (NilFL, mapFL_FL Normal xs)+ _z -> --traceDoc (greenText "mac1 z" $$ showPatch _z) $+ Nothing+ mac (p:<:ps) xs goneby = --traceDoc (greenText "mac ps" $$ showPatch ps $$+ -- greenText "p" $$ showPatch p $$+ -- greenText "xs" $$ showPatch xs $$+ -- greenText "goneby" $$ showPatch goneby) $+ case commuteFL (p :> mapFL_FL Normal xs) of+ Left _ -> case genCommuteWhatWeCanRL commute_no_conflicts (ps :> p) of+ a:>p':>b ->+ do (b',xs') <- mac b xs goneby+ let pa = join_effects $ p':<:a+ --traceDoc (greenText "foo1" $$+ -- showPatch pa) $ Just ()+ NilFL <- return pa+ return (reverseRL (p':<:a)+>+b', xs')+ `mplus` do NilFL <- return goneby+ NilFL <- return $ join_effects (p:<:ps)+ return (reverseRL (p:<:ps),+ mapFL_FL Normal xs)+ Right (l:>p'') ->+ case allNormal l of+ Just xs'' -> mac ps xs'' (p'':>:goneby)+ Nothing ->+ case genCommuteWhatWeCanRL commute_no_conflicts (ps :> p) of+ a:>p':>b ->+ do (b',xs') <- mac b xs goneby+ let pa = join_effects $ p':<:a+ --traceDoc (greenText "foo2" $$+ -- showPatch pa) $ Just ()+ NilFL <- return pa+ return $ (reverseRL (p':<:a)+>+b', xs')++geteff :: [Non RealPatch C(x)] -> FL Prim C(x y) -> ([Non RealPatch C(x)], FL RealPatch C(x y))+geteff _ NilFL = ([],NilFL)+geteff ix (x:>:xs) | Just ix' <- mapM (remP (Normal x)) ix+ = --traceDoc (greenText "I got rid of x" $$ showPatch x) $+ case geteff ix' xs of+ (ns,xs') -> (non (Normal x) : map (addP (Normal x)) ns,+ Normal x :>: xs')+geteff ix xx = case mergeConflictingNons ix of+ Nothing -> errorDoc $ redText "mergeConflictingNons failed in geteff with ix" $$+ showNons ix $$ redText "xx" $$ showPatch xx+ Just rix -> case mergeAfterConflicting rix xx of+ Just (a,x) -> (map (addPs (reverseFL a)) $ toNons x,+ a +>+ x)+ Nothing -> errorDoc $ redText "mergeAfterConflicting failed in geteff"$$+ redText "where ix" $$ showNons ix $$+ redText "and xx" $$ showPatch xx $$+ redText "and rix" $$ showPatch rix++xx2nons :: [Non RealPatch C(x)] -> FL Prim C(x y) -> [Non RealPatch C(x)]+xx2nons ix xx = fst $ geteff ix xx++xx2patches :: [Non RealPatch C(x)] -> FL Prim C(x y) -> FL RealPatch C(x y)+xx2patches ix xx = snd $ geteff ix xx++-- | If @xs@ consists only of 'Normal' patches, 'allNormal' @xs@ returns+-- @Just pxs@ those patches (so @lengthFL pxs == lengthFL xs@).+-- Otherwise, it returns 'Nothing'.+allNormal :: FL RealPatch C(x y) -> Maybe (FL Prim C(x y))+allNormal (Normal x:>:xs) = (x :>:) `fmap` allNormal xs+allNormal NilFL = Just NilFL+allNormal _ = Nothing++-- | This is used for unit-testing and for internal sanity checks+is_consistent :: RealPatch C(x y) -> Maybe Doc+is_consistent (Normal _) = Nothing+is_consistent (Duplicate _) = Nothing+is_consistent (Etacilpud _) = Nothing+is_consistent (Conflictor im mm m@(Non deps _))+ | not $ everyone_conflicts im = Just $ redText "Someone doesn't conflict in im in is_consistent"+ | Just _ <- remPs rmm m, _:>:_ <- mm = Just $ redText "m doesn't conflict with mm in is_consistent"+ | any (\x -> any (x `conflicts_with`) nmm) im+ = Just $ redText "mm conflicts with im in is_consistent where nmm is" $$+ showNons nmm+ | Nothing <- (nmm ++ im) `minus` toNons deps = Just $ redText "dependencies not in conflict:" $$+ showNons (toNons deps) $$+ redText "compared with deps itself:" $$+ showPatch deps+ | otherwise = case all_conflicts_with m im of+ (im1,[]) | im1 `eqSet` im -> Nothing+ (_,imnc) -> Just $ redText "m doesn't conflict with im in is_consistent. unconflicting:"+ $$ showNons imnc+ where (nmm, rmm) = geteff im mm+is_consistent c@(InvConflictor _ _ _) = is_consistent (invert c)++everyone_conflicts :: [Non RealPatch C(x)] -> Bool+everyone_conflicts [] = True+everyone_conflicts (x:xs) = case all_conflicts_with x xs of+ ([],_) -> False+ (_,xs') -> everyone_conflicts xs'++prim2real :: Prim C(x y) -> RealPatch C(x y)+prim2real = Normal++instance Patchy RealPatch++instance MyEq p => Eq (Sealed (p C(x))) where+ (Sealed x) == (Sealed y) | IsEq <- x =\/= y = True+ | otherwise = False++merge_with :: Non RealPatch C(x) -> [Non RealPatch C(x)] -> Sealed (FL Prim C(x))+merge_with p [] = effect `mapSeal` unNon p+merge_with p xs = mergeall $ map unNon $ (p:) $ unconflicting_of $+ filter (\x -> not (p `depends_upon` x) && not (p `conflicts_with` x)) xs+ where mergeall :: [Sealed (FL RealPatch C(x))] -> Sealed (FL Prim C(x))+ mergeall [Sealed x] = Sealed $ effect x+ mergeall [] = Sealed NilFL+ mergeall (Sealed x:Sealed y:rest) = case merge (x :\/: y) of+ y' :/\: _ -> mergeall (Sealed (x+>+y'):rest)+ unconflicting_of [] = []+ unconflicting_of (q:qs) = case all_conflicts_with q qs of+ ([],_) -> q:qs+ (_,nc) -> unconflicting_of nc++instance Conflict RealPatch where+ conflictedEffect (Duplicate (Non _ x)) = [IsC Duplicated x]+ conflictedEffect (Etacilpud _) = impossible+ conflictedEffect (Conflictor _ _ (Non _ x)) = [IsC Conflicted x]+ conflictedEffect (InvConflictor _ _ _) = impossible+ conflictedEffect (Normal x) = [IsC Okay x]+ resolve_conflicts (Conflictor ix xx x) = [mangle_unravelled unravelled : unravelled]+ where unravelled = nub $ filter isn $ map (`merge_with` (x:ix++nonxx)) (x:ix++nonxx)+ nonxx = nonxx_ (nonxx_aux ix xx)+ nonxx_aux :: [Non RealPatch C(x)] -> FL Prim C(x y) -> RL RealPatch C(x y)+ nonxx_aux a b = reverseFL $ xx2patches a b+ nonxx_ :: RL RealPatch C(x y) -> [Non RealPatch C(x)]+ nonxx_ NilRL = []+ nonxx_ ((Normal q) :<: qs) = [Non (reverseRL qs) q]+ nonxx_ _ = []+ isn :: Sealed (FL p C(x)) -> Bool+ isn (Sealed NilFL) = False+ isn _ = True+ resolve_conflicts _ = []++ -- cA+ commute_no_conflicts (Duplicate x :> Duplicate y) = Just (Duplicate y :> Duplicate x)+ commute_no_conflicts (Etacilpud x :> Duplicate y) = Just (Duplicate y :> Etacilpud x)+ commute_no_conflicts (Duplicate x :> Etacilpud y) = Just (Etacilpud y :> Duplicate x)+ commute_no_conflicts (Etacilpud x :> Etacilpud y) = Just (Etacilpud y :> Etacilpud x)+ -- cB+ commute_no_conflicts (x :> Duplicate d) = if d == addP (invert x) (non x)+ then Just (x :> Duplicate d)+ else do d' <- remP (invert x) d+ return (Duplicate d' :> x)+ commute_no_conflicts (Duplicate d' :> x) = Just (x :> Duplicate (addP (invert x) d'))+ commute_no_conflicts c@(Etacilpud _ :> _) = invertCommuteNC c+ commute_no_conflicts c@(_ :> Etacilpud _) = invertCommuteNC c+ -- cE+ commute_no_conflicts (Normal x :> Normal y) = do y' :> x' <- commute (x :> y)+ return (Normal y' :> Normal x')+ -- cF -- involves a conflict+ -- cG+ commute_no_conflicts (Normal x :> Conflictor iy yy y) =+ case commuteFL (x :> invert yy) of+ Right (iyy' :> x') -> do+ y':iy' <- mapM (Normal x' >*) (y:iy)+ return (Conflictor iy' (invert iyy') y' :> Normal x')+ _ -> Nothing+ -- cFi+cGi -- handle with previous two pattern matches+ commute_no_conflicts c@(InvConflictor _ _ _ :> Normal _) = invertCommuteNC c+ -- icG FIXME: where is icF?+ commute_no_conflicts (Conflictor iy' yy' y' :> Normal x') =+ do x :> iyy <- commuteRL (invertFL yy' :> x')+ y:iy <- mapM (*> Normal x') (y':iy')+ return (Normal x :> Conflictor iy (invertRL iyy) y)+ -- icGi -- handle with previous pattern match+ commute_no_conflicts c@(Normal _ :> InvConflictor _ _ _) = invertCommuteNC c+ -- cH -- this involves a conflict commute+ -- cI+ commute_no_conflicts (Conflictor ix xx x :> Conflictor iy yy y) =+ do xx' :> yy' <- commute (yy :> xx)+ x':ix' <- mapM (yy >>*) (x:ix)+ y':iy' <- mapM (*>> xx') (y:iy)+ False <- return $ any (conflicts_with y) (x':ix')+ False <- return $ any (conflicts_with x') iy+ return (Conflictor iy' yy' y' :> Conflictor ix' xx' x')+ -- cHi+cIi uses previous two matches+ commute_no_conflicts c@(InvConflictor _ _ _ :> InvConflictor _ _ _) = invertCommuteNC c+ -- cJ+ commute_no_conflicts (InvConflictor ix xx x :> Conflictor iy yy y) =+ do iyy' :> xx' <- commute (xx :> invert yy)+ y':iy' <- mapM (xx' >>*) (y:iy)+ x':ix' <- mapM (invertFL iyy' >>*) (x:ix)+ False <- return $ any (conflicts_with y') (x':ix')+ False <- return $ any (conflicts_with x') iy'+ return (Conflictor iy' (invert iyy') y' :> InvConflictor ix' xx' x')+ -- icJ+ commute_no_conflicts (Conflictor iy' yy' y' :> InvConflictor ix' xx' x') =+ do xx :> iyy <- commute (invert yy' :> xx')+ y:iy <- mapM (*>> xx') (y':iy')+ x:ix <- mapM (*>> yy') (x':ix')+ False <- return $ any (conflicts_with y') (x':ix')+ False <- return $ any (conflicts_with x') iy'+ return (InvConflictor ix xx x :> Conflictor iy (invert iyy) y)++instance FromPrim RealPatch where+ fromPrim = prim2real+instance ToFromPrim RealPatch where+ toPrim (Normal p) = Just p+ toPrim _ = Nothing++instance MyEq RealPatch where+ (Duplicate x) =\/= (Duplicate y) | x == y = IsEq+ (Etacilpud x) =\/= (Etacilpud y) | x == y = IsEq+ (Normal x) =\/= (Normal y) = x =\/= y+ (Conflictor cx xx x) =\/= (Conflictor cy yy y)+ | map (add $ invertFL xx) cx `eqSet`+ map (add $ invertFL yy) cy &&+ add (invert xx) x == add (invert yy) y = xx =/\= yy+ (InvConflictor cx xx x) =\/= (InvConflictor cy yy y)+ | cx `eqSet` cy && x == y = xx =\/= yy+ _ =\/= _ = NotEq++eqSet :: Eq a => [a] -> [a] -> Bool+eqSet [] [] = True+eqSet (x:xs) xys | Just ys <- remove1 x xys = eqSet xs ys+eqSet _ _ = False++remove1 :: Eq a => a -> [a] -> Maybe [a]+remove1 x (y:ys) | x == y = Just ys+ | otherwise = (y :) `fmap` remove1 x ys+remove1 _ [] = Nothing++minus :: Eq a => [a] -> [a] -> Maybe [a]+minus xs [] = Just xs+minus xs (y:ys) = do xs' <- remove1 y xs+ xs' `minus` ys++invertNon :: Non RealPatch C(x) -> Non RealPatch C(x)+invertNon (Non c x)+ | Just rc' <- removeRL nix (reverseFL c) = Non (reverseRL rc') (invert x)+ | otherwise = addPs (Normal x :<: reverseFL c) $ non nix+ where nix = Normal $ invert x++nonTouches :: Non RealPatch C(x) -> [FilePath]+nonTouches (Non c x) = list_touched_files (c +>+ fromPrim x :>: NilFL)++toNons :: (Conflict p, Patchy p, ToFromPrim p, Nonable p) => FL p C(x y) -> [Non p C(x)]+toNons xs = map lastNon $ initsFL xs+ where lastNon :: (Conflict p, Patchy p, Nonable p) => Sealed ((p :> FL p) C(x)) -> Non p C(x)+ lastNon (Sealed xxx) = case lastNon_aux xxx of+ deps :> p :> _ -> case non p of+ Non NilFL pp -> Non (reverseRL deps) pp+ Non ds pp -> errorDoc $ redText "Weird case in toNons" $$+ redText "please report this bug!" $$+ (case xxx of+ z:>zs -> showPatch (z:>:zs)) $$+ redText "ds are" $$ showPatch ds $$+ redText "pp is" $$ showPatch pp+ reverseFoo :: (p :> FL p) C(x y) -> (RL p :> p) C(x y)+ reverseFoo (p :> ps) = rf NilRL p ps+ where rf :: RL p C(a b) -> p C(b c) -> FL p C(c d) -> (RL p :> p) C(a d)+ rf rs l NilFL = rs :> l+ rf rs x (y:>:ys) = rf (x:<:rs) y ys+ lastNon_aux :: Commute p => (p :> FL p) C(x y) -> (RL p :> p :> RL p) C(x y)+ lastNon_aux = commuteWhatWeCanRL . reverseFoo++initsFL :: Patchy p => FL p C(x y) -> [Sealed ((p :> FL p) C(x))]+initsFL NilFL = []+initsFL (x:>:xs) = Sealed (x:>NilFL) : map (\ (Sealed (y:>xs')) -> Sealed (x:>y:>:xs')) (initsFL xs)++fromNons :: [Non RealPatch C(x)] -> Maybe (Sealed (FL Prim C(x)))+fromNons [] = Just $ Sealed $ NilFL+fromNons ns = do (Sealed p, ns') <- pullInContext ns+ ns'' <- mapM (remP $ fromPrim p) ns'+ Sealed ps <- fromNons ns''+ return $ Sealed $ p :>: ps++pullInContext :: [Non RealPatch C(x)] -> Maybe (Sealed (Prim C(x)), [Non RealPatch C(x)])+pullInContext (Non NilFL p:ns) = Just (Sealed p, ns)+pullInContext (n:ns) = do (sp,ns') <- pullInContext ns+ return (sp, n:ns')+pullInContext [] = Nothing++filterConflictsFL :: Non RealPatch C(x) -> FL Prim C(x y) -> (FL Prim :> FL Prim) C(x y)+filterConflictsFL _ NilFL = NilFL :> NilFL+filterConflictsFL n (p:>:ps)+ | Just n' <- remP (fromPrim p) n = case filterConflictsFL n' ps of+ p1 :> p2 -> p:>:p1 :> p2+ | otherwise = case commuteWhatWeCanFL (p :> ps) of+ p1 :> p' :> p2 -> case filterConflictsFL n p1 of+ p1a :> p1b -> p1a :> p1b +>+ p' :>: p2++instance Invert RealPatch where+ invert (Duplicate d) = Etacilpud d+ invert (Etacilpud d) = Duplicate d+ invert (Normal p) = Normal (invert p)+ invert (Conflictor x c p) = InvConflictor x c p+ invert (InvConflictor x c p) = Conflictor x c p+ identity = Normal identity++instance Commute RealPatch where+-- commute (x :> y) | traceDoc (greenText "commuting x" $$ showPatch x $$+-- greenText "with y" $$ showPatch y) False = undefined+ commute (x :> y) | Just (y' :> x') <- commute_no_conflicts (assertConsistent x :> assertConsistent y) = Just (y' :> x')+ -- cF+ commute (Normal x :> Conflictor a1'nop2 n1'x p1') -- these patches conflicted+ | Just rn1' <- removeRL x (reverseFL n1'x) =+ do let p2:n1nons = reverse $ xx2nons a1'nop2 $ reverseRL (x:<:rn1')+ a2 = p1':a1'nop2++n1nons+ case (a1'nop2, reverseRL rn1', p1') of+ ([], NilFL, Non c y) | NilFL <- join_effects c ->+ Just (Normal y :> Conflictor a1'nop2 (y:>:NilFL) p2)+ (a1,n1,_) -> Just (Conflictor a1 n1 p1' :> Conflictor a2 NilFL p2)+ -- cFi -- handle with previous pattern match+ commute c@(InvConflictor _ _ _ :> Normal _) = invertCommute c+ -- cH+ commute (Conflictor a1 n1 p1 :> Conflictor a2 n2 p2)+ | Just a2_minus_p1 <- remove1 p1' a2,+ not (p2 `depends_upon` p1') =+ do let n1nons = map (add n2) $ xx2nons a1 n1+ n2nons = xx2nons a2 n2+ Just a2_minus_p1n1 = a2_minus_p1 `minus` n1nons+ n2n1 = n2 +>+ n1+ a1' = map (add n2) a1+ p2ooo = remNons a1' p2+ n1' :> n2' <- return $ filterConflictsFL p2ooo n2n1+ let n1'n2'nons = xx2nons a2_minus_p1n1 (n1'+>+n2')+ n1'nons = take (lengthFL n1') n1'n2'nons+ n2'nons = drop (lengthFL n1') n1'n2'nons+ Just a1'nop2 = (a2++n2nons) `minus` (p1':n1'nons)+ Just a2'o = --traceDoc (greenText "\n\nConflictor a1 n1 p1 is" $$+ -- showPatch (assertConsistent $ Conflictor a1 n1 p1) $$+ -- greenText "and Conflictor a2 n2 p2 is" $$+ -- showPatch (assertConsistent $ Conflictor a2 n2 p2) $$+ -- greenText "where n2'nons is" $$ showNons n2'nons $$+ -- greenText "and others are" $$+ -- showNons (fst $ all_conflicts_with p2 $ a2_minus_p1++n2nons) $$+ -- greenText "These came from" $$+ -- showNons (a2_minus_p1++n2nons) $$+ -- greenText "n1'n2'nons" $$ showNons n1'n2'nons $$+ -- greenText "from n1' :> n2'" $$+ -- showPatch n1' $$ greenText ":>" $$ showPatch n2' $$+ -- greenText "p2" $$ showNon p2 $$+ -- greenText "p2 fixed" $$ showNon p2ooo $$+ -- -- greenText "pren1" $$ showPatch pren1 $$+ -- greenText "n1'" $$ showPatch n1' $$+ -- greenText "p2" $$ showNon p2 + -- ) + (fst $ all_conflicts_with p2 $ a2_minus_p1++n2nons) `minus` n2'nons+ Just a2' = mapM (remPs (xx2patches a1'nop2 n1')) $+ a2'o+ Just p2' = remPs (xx2patches a1'nop2 n1') p2+ case (a2', n2', p2') of+ ([], NilFL, Non c x) | NilFL <- join_effects c ->+ Just (Normal x :> Conflictor a1'nop2 (n1'+>+x:>:NilFL) p1')+ | otherwise -> impossible+ _ -> Just (Conflictor a2' n2' p2' :> Conflictor (p2:a1'nop2) n1' p1')+ where (_,rpn2) = geteff a2 n2+ p1' = addPs (reverseFL rpn2) p1+ -- cHi -- uses previous match+ commute c@(InvConflictor _ _ _ :> InvConflictor _ _ _) = invertCommute c+ commute _ = Nothing++ merge (InvConflictor _ _ _ :\/: _) = impossible+ merge (_ :\/: InvConflictor _ _ _) = impossible+ merge (Etacilpud _ :\/: _) = impossible+ merge (_ :\/: Etacilpud _) = impossible+-- merge (x :\/: y) | traceDoc (greenText "merging x" $$ showPatch x $$+-- greenText "with y" $$ showPatch y) False = impossible+ -- mA+ merge (Duplicate a :\/: Duplicate b) = Duplicate b :/\: Duplicate a+ -- mB+ merge (Duplicate a :\/: b) = b :/\: Duplicate (addP (invert b) a) -- FIXME ???+ -- smB+ merge m@(_ :\/: Duplicate _) = swapMerge m+ -- mC+-- merge _ | traceDoc (greenText "about to look for conflictingness") False = impossible+ merge (x :\/: y) | Just (y' :> ix') <- commute (invert (assertConsistent x) :> assertConsistent y),+ Just (y'' :> _) <- commute (x :> y'),+ IsEq <- y'' =\/= y = --traceDoc (greenText "These didn't conflict") $+ assertConsistent y' :/\: invert (assertConsistent ix')+ | IsEq <- x =\/= y,+ n <- addP (invert x) $ non x =+ --traceDoc (greenText "Found duplicate") $+ Duplicate n :/\: Duplicate n+-- merge (x :\/: y) | traceDoc (greenText "trying to merging x" $$ showPatch x $$+-- greenText "which conflicts with y" $$ showPatch y) False = impossible+ -- mD+ merge (Normal x :\/: Normal y) =+ Conflictor [] (x:>:NilFL) (non $ Normal y) :/\: Conflictor [] (y:>:NilFL) (non $ Normal x)+ -- mG+ merge (Normal x :\/: Conflictor iy yy y) =+ --traceDoc (greenText "merging Normal x" $$ showPatch x $$+ -- greenText "and Conflictor iy yy y" $$ showPatch (Conflictor iy yy y)) $+ Conflictor iy yyx y :/\: Conflictor (y:iy++nyy) NilFL x'+ where yyx = yy +>+ x:>:NilFL+ (x':nyy) = reverse $ xx2nons iy yyx+ -- smE+smG+ merge m@(Conflictor _ _ _ :\/: Normal _) = swapMerge m+-- merge (x :\/: y) | traceDoc (greenText "still trying to merge x" $$ showPatch x $$+-- greenText "with y" $$ showPatch y) False = impossible+ -- mH see also cH+ merge (Conflictor ix xx x :\/: Conflictor iy yy y) =+ case pullCommonRL (reverseFL xx) (reverseFL yy) of+ CommonRL rxx1 ryy1 c ->+ case commuteRLFL (ryy1 :> invertRL rxx1) of+ Just (ixx' :> ryy') ->+ let xx' = invert ixx'+ yy' = reverseRL ryy'+ y':iy' = map (add $ invertFL ixx') (y:iy)+ x':ix' = map (add ryy') (x:ix)+ nyy' = xx2nons iy' yy'+ nxx' = xx2nons ix' xx'+ icx = drop (lengthRL rxx1) $ xx2nons ix (reverseRL $ c+<+rxx1)+ ic' = map (add ryy') icx+ ixy' = ic' ++ (iy'+++ix')+ -- +++ above is a more efficient version of nub+ -- (iy'++ix') given that we know each element shows up+ -- only once in either list.+ in --traceDoc (greenText "here I am! and so is ixy'" $$ showNons ixy' $$+ -- greenText "and iy" $$ showNons iy $$ greenText (show $ length iy) $$+ -- greenText "and ix" $$ showNons ix $$+ -- greenText "and iy'" $$ showNons iy' $$+ -- greenText "and ix'" $$ showNons ix' $$+ -- greenText "and ic'" $$ showNons ic'+ -- ) $+ Conflictor (x':ixy'++nxx') yy' y' :/\: Conflictor (y':ixy'++nyy') xx' x'+ Nothing -> impossible pullInContext fromNons+-- merge _ = error "haven't finished fixing merge"++ list_touched_files (Duplicate p) = nonTouches p+ list_touched_files (Etacilpud p) = nonTouches p+ list_touched_files (Normal p) = list_touched_files p+ list_touched_files (Conflictor x c p) =+ nubsort $ concatMap nonTouches x ++ list_touched_files c ++ nonTouches p+ list_touched_files (InvConflictor x c p) =+ nubsort $ concatMap nonTouches x ++ list_touched_files c ++ nonTouches p++{-+all_conflicts_withFL :: FL Prim C(x y) -> [Non RealPatch C(x)]+ -> ([Non RealPatch C(x)], [Non RealPatch C(x)])+all_conflicts_withFL xx ns = case partition f ns of+ ([],nc) -> ([],nc)+ (c,nc) -> case acw c nc of+ (c',nc') -> (c++c',nc')+ where acw (y:ys) zs = case all_conflicts_with y zs of+ (c,nc) -> case acw ys nc of+ (c',nc') -> (c++c',nc')+ acw [] zs = ([],zs)+ f (Non c p) = case commuteRLFL (invertFL c :> mapFL_FL Normal xx) of+ Nothing -> True+ Just (xx' :> _) -> case commuteFL (Normal (invert p) :> xx') of+ Nothing -> True+ Just _ -> False+-}+all_conflicts_with :: Non RealPatch C(x) -> [Non RealPatch C(x)]+ -> ([Non RealPatch C(x)], [Non RealPatch C(x)])+all_conflicts_with x ys = acw $ partition (conflicts_with x) ys+ where acw ([],nc) = ([],nc)+ acw (c:cs, nc) = case all_conflicts_with c nc of+ (c1,nc1) -> case acw (cs, nc1) of+ (xs',nc') -> (c:c1++xs',nc')++conflicts_with :: Non RealPatch C(x) -> Non RealPatch C(x) -> Bool+conflicts_with x y | x `depends_upon` y || y `depends_upon` x = False+conflicts_with x (Non cy y) =+ case remPs cy x of+ Just (Non cx' x') -> case commuteFL (fromPrim (invert y) :> cx' +>+ fromPrim x' :>: NilFL) of+ Right _ -> False+ Left _ -> True+ Nothing -> True++depends_upon :: Non RealPatch C(x) -> Non RealPatch C(x) -> Bool+depends_upon (Non xs _) (Non ys y) =+ case remove_subsequenceFL (ys +>+ fromPrim y :>: NilFL) xs of+ Just _ -> True+ Nothing -> False++(+++) :: Eq a => [a] -> [a] -> [a]+[] +++ x = x+x +++ [] = x+(x:xs) +++ xys | Just ys <- remove1 x xys = x : (xs +++ ys)+ | otherwise = x : (xs +++ xys)++swapMerge :: (RealPatch :\/: RealPatch) C(x y) -> (RealPatch :/\: RealPatch) C(x y)+swapMerge (x :\/: y) = case merge (y :\/: x) of x' :/\: y' -> y' :/\: x'++invertCommute :: (RealPatch :> RealPatch) C(x y) -> Maybe ((RealPatch :> RealPatch) C(x y))+invertCommute (x :> y) = do ix' :> iy' <- commute (invert y :> invert x)+ return (invert iy' :> invert ix')++invertCommuteNC :: (RealPatch :> RealPatch) C(x y) -> Maybe ((RealPatch :> RealPatch) C(x y))+invertCommuteNC (x :> y) = do ix' :> iy' <- commute_no_conflicts (invert y :> invert x)+ return (invert iy' :> invert ix')++-- | 'pullCommon' @xs ys@ returns the set of patches that can be commuted+-- out of both @xs@ and @ys@ along with the remnants of both lists+pullCommon :: Patchy p => FL p C(o x) -> FL p C(o y) -> Common p C(o x y)+pullCommon NilFL ys = Common NilFL NilFL ys+pullCommon xs NilFL = Common NilFL xs NilFL+pullCommon (x:>:xs) xys | Just ys <- removeFL x xys = case pullCommon xs ys of+ Common c xs' ys' -> Common (x:>:c) xs' ys'+pullCommon (x:>:xs) ys = case commuteWhatWeCanFL (x :> xs) of+ xs1:>x':>xs2 -> case pullCommon xs1 ys of+ Common c xs1' ys' -> Common c (xs1'+>+x':>:xs2) ys'++-- | 'Common' @cs xs ys@ represents two sequences of patches that have @cs@ in common,+-- in other words @cs +>+ xs@ and @cs +>+ ys@+data Common p C(o x y) where+ Common :: FL p C(o i) -> FL p C(i x) -> FL p C(i y) -> Common p C(o x y)++-- | 'pullCommonRL' @xs ys@ returns the set of patches that can be commuted+-- out of both @xs@ and @ys@ along with the remnants of both lists+pullCommonRL :: Patchy p => RL p C(x o) -> RL p C(y o) -> CommonRL p C(x y o)+pullCommonRL NilRL ys = CommonRL NilRL ys NilRL+pullCommonRL xs NilRL = CommonRL xs NilRL NilRL+pullCommonRL (x:<:xs) xys+ | Just ys <- removeRL x xys = case pullCommonRL xs ys of+ CommonRL xs' ys' c -> CommonRL xs' ys' (x:<:c)+pullCommonRL (x:<:xs) ys =+ case commuteWhatWeCanRL (xs :> x) of+ xs1:>x':>xs2 -> case pullCommonRL xs2 ys of+ CommonRL xs2' ys' c -> CommonRL (xs2'+<+x':<:xs1) ys' c++-- | 'CommonRL' @xs ys cs@' represents two sequences of patches that have @cs@ in common,+-- in other words @xs +<+ cs@ and @ys +<+ cs@+data CommonRL p C(x y f) where+ CommonRL :: RL p C(x i) -> RL p C(y i) -> RL p C(i f) -> CommonRL p C(x y f)++instance Apply RealPatch where+ apply opts p = apply opts (effect p)+ applyAndTryToFixFL (Normal p) = mapMaybeSnd (mapFL_FL Normal) `liftM` applyAndTryToFixFL p+ applyAndTryToFixFL x = do apply [] x; return Nothing++instance ShowPatch RealPatch where+ showPatch (Duplicate d) = blueText "duplicate" $$ showNon d+ showPatch (Etacilpud d) = blueText "etacilpud" $$ showNon d+ showPatch (Normal p) = showPrim NewFormat p+ showPatch (Conflictor i NilFL p) =+ blueText "conflictor" <+> showNons i <+> blueText "[]" $$ showNon p+ showPatch (Conflictor i cs p) =+ blueText "conflictor" <+> showNons i <+> blueText "[" $$+ showPatch cs $$+ blueText "]" $$+ showNon p+ showPatch (InvConflictor i NilFL p) =+ blueText "rotcilfnoc" <+> showNons i <+> blueText "[]" $$ showNon p+ showPatch (InvConflictor i cs p) =+ blueText "rotcilfnoc" <+> showNons i <+> blueText "[" $$+ showPatch cs $$+ blueText "]" $$+ showNon p+ showContextPatch s (Normal p) = showContextPatch s p+ showContextPatch _ c = showPatch c++instance ReadPatch RealPatch where+ readPatch' want_eof =+ do s <- peek_input+ case fmap (BC.unpack . fst) $ my_lex s of+ Just "duplicate" ->+ do work my_lex+ p <- readNon+ return $ (Sealed . Duplicate) `fmap` p+ Just "etacilpud" ->+ do work my_lex+ p <- readNon+ return $ (Sealed . Etacilpud) `fmap` p+ Just "conflictor" ->+ do work my_lex+ --let tracePeek x = do y <- peek_input+ -- traceDoc (greenText x $$ greenText (show $ BC.unpack y)) return ()+ i <- readNons+ Just (Sealed ps) <- bracketedFL (fromIntegral $ fromEnum '[') (fromIntegral $ fromEnum ']')++ Just p <- readNon+ return $ Just $ Sealed $ Conflictor i (unsafeCoerceP ps) p+ Just "rotcilfnoc" ->+ do work my_lex+ i <- readNons+ Just (Sealed ps) <- bracketedFL (fromIntegral $ fromEnum '[') (fromIntegral $ fromEnum ']')+ Just p <- readNon+ return $ Just $ Sealed $ InvConflictor i ps p+ _ -> do mp <- readPrim NewFormat want_eof+ case mp of+ Just p -> return $ Just $ Normal `mapSeal` p+ Nothing -> return Nothing++instance Show (RealPatch C(x y)) where+ show p = renderString $ showPatch p++instance Show2 RealPatch where+ show2 = show++instance Nonable RealPatch where+ non (Duplicate d) = d+ non (Etacilpud d) = invertNon d -- FIXME !!! ???+ non (Normal p) = Non NilFL p+ non (Conflictor _ xx x) = add (invertFL xx) x+ non (InvConflictor _ _ n) = invertNon n++instance Effect RealPatch where+ effect (Duplicate _) = NilFL+ effect (Etacilpud _) = NilFL+ effect (Normal p) = effect p+ effect (Conflictor _ e _) = invert e+ effect (InvConflictor _ e _) = e+ effectRL (Duplicate _) = NilRL+ effectRL (Etacilpud _) = NilRL+ effectRL (Normal p) = effectRL p+ effectRL (Conflictor _ e _) = invertFL e+ effectRL (InvConflictor _ e _) = reverseFL e+ isHunk rp = do Normal p <- return rp+ isHunk p
@@ -0,0 +1,50 @@+-- Copyright (C) 2003 David Roundy+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.++{-# OPTIONS_GHC -cpp #-}+{-# LANGUAGE CPP #-}++#include "gadts.h"++module Darcs.Patch.Set ( PatchSet, SealedPatchSet ) where++import Darcs.Hopefully ( PatchInfoAnd )+import Darcs.Ordered ( RL )+import Darcs.Sealed ( Sealed )++-- | A PatchSet is in reverse order, plus has information about which+-- tags are clean, meaning all patches applied prior to them are in+-- the tag itself, so we can stop reading at that point. Just to+-- clarify, the first patch in a PatchSet is the one most recently+-- applied to the repo.+--+-- 'PatchSet's have the property that if+-- @+-- (info $ last $ head a) == (info $ last $ head b)+-- @+-- then @(tail a)@ and @(tail b)@ are identical repositories+--+-- Questions:+--+-- Does this mean that in a patch set such as @[[a b t1 c d e t2][f g+-- t3] [h i]]@, t1, t2 and t3 are tags, and t2 and t3 are clean?+--+-- Can we have PatchSet with length at least 3?+-- Florent+type PatchSet p C(x) = RL (RL (PatchInfoAnd p)) C(() x)++type SealedPatchSet p = Sealed (RL (RL (PatchInfoAnd p)) C(()))
@@ -0,0 +1,89 @@+% Copyright (C) 2002-2005 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.+++\begin{code}+{-# OPTIONS_GHC -cpp -fno-warn-orphans -fglasgow-exts #-}+{-# LANGUAGE CPP #-}++module Darcs.Patch.Show ( showPatch_, showNamedPrefix )+ where++import Prelude hiding ( pi )++import Printer ( Doc, renderString, vcat,+ text, blueText,+ ($$), (<+>) )+import Darcs.Patch.Core ( Patch(..) )+import Darcs.Patch.Prim ( showPrim, FileNameFormat(..) )+import Darcs.Patch.Info ( PatchInfo, showPatchInfo )+import Darcs.Ordered ( FL(NilFL), mapFL )+#include "gadts.h"+\end{code}++\section{Patch string formatting}++Of course, in order to store our patches in a file, we'll have to save them+as some sort of strings. The convention is that each patch string will end+with a newline, but on parsing we skip any amount of whitespace between+patches.+\begin{code}+instance Show (Patch C(x y)) where+ show p = renderString (showPatch_ p) ++ "\n"++showPatch_ :: Patch C(a b) -> Doc+showPatch_ (PP p) = showPrim OldFormat p+showPatch_ (ComP NilFL) = blueText "{" $$ blueText "}"+showPatch_ (ComP ps) = blueText "{"+ $$ vcat (mapFL showPatch_ ps)+ $$ blueText "}"+showPatch_ (Merger _ _ p1 p2) = showMerger "merger" p1 p2+showPatch_ (Regrem _ _ p1 p2) = showMerger "regrem" p1 p2+\end{code}++\paragraph{Merger patches}+Merge two patches. The MERGERVERSION is included to allow some degree of+backwards compatibility if the merger algorithm needs to be changed.+\begin{verbatim}+merger MERGERVERSION+<first patch>+<second patch>+\end{verbatim}+\begin{code}+showMerger :: String -> Patch C(a b) -> Patch C(d e) -> Doc+showMerger merger_name p1 p2 =+ blueText merger_name <+> text "0.0" <+> blueText "("+ $$ showPatch_ p1+ $$ showPatch_ p2+ $$ blueText ")"+\end{code}++\paragraph{Named patches}++Named patches are displayed as a ``patch id'' which is in square brackets,+followed by a patch. Optionally, after the patch id (but before the patch+itself) can come a list of dependencies surrounded by angle brackets. Each+dependency consists of a patch id.++\begin{code}+showNamedPrefix :: PatchInfo -> [PatchInfo] -> Doc+showNamedPrefix n d = showPatchInfo n+ $$ blueText "<"+ $$ vcat (map showPatchInfo d)+ $$ blueText ">"++\end{code}
@@ -0,0 +1,90 @@+-- Copyright (C) 2002-2004 David Roundy+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.++{-# OPTIONS_GHC -cpp #-}+{-# LANGUAGE CPP #-}++#include "gadts.h"++module Darcs.Patch.TouchesFiles ( look_touch, choose_touching,+ select_touching,+ deselect_not_touching, select_not_touching,+ ) where+import Data.List ( sort )++import Darcs.Patch.Choices ( PatchChoices, Tag, TaggedPatch,+ patch_choices, tag, get_choices,+ force_firsts, force_lasts, tp_patch,+ )+import Darcs.Patch ( Patchy, apply_to_filepaths, list_touched_files )+import Darcs.Ordered ( FL(..), (:>)(..), mapFL_FL, (+>+) )+import Darcs.Sealed ( Sealed, seal )++select_touching :: Patchy p => [FilePath] -> PatchChoices p C(x y) -> PatchChoices p C(x y)+select_touching [] pc = pc+select_touching files pc = force_firsts xs pc+ where ct :: Patchy p => [FilePath] -> FL (TaggedPatch p) C(x y) -> [Tag]+ ct _ NilFL = []+ ct fs (tp:>:tps) = case look_touch fs (tp_patch tp) of+ (True, fs') -> tag tp:ct fs' tps+ (False, fs') -> ct fs' tps+ xs = case get_choices pc of+ _ :> mc :> lc -> ct (map fix files) (mc +>+ lc)++deselect_not_touching :: Patchy p => [FilePath] -> PatchChoices p C(x y) -> PatchChoices p C(x y)+deselect_not_touching [] pc = pc+deselect_not_touching files pc = force_lasts xs pc+ where ct :: Patchy p => [FilePath] -> FL (TaggedPatch p) C(x y) -> [Tag]+ ct _ NilFL = []+ ct fs (tp:>:tps) = case look_touch fs (tp_patch tp) of+ (True, fs') -> ct fs' tps+ (False, fs') -> tag tp:ct fs' tps+ xs = case get_choices pc of+ fc :> mc :> _ -> ct (map fix files) (fc +>+ mc)++select_not_touching :: Patchy p => [FilePath] -> PatchChoices p C(x y) -> PatchChoices p C(x y)+select_not_touching [] pc = pc+select_not_touching files pc = force_firsts xs pc+ where ct :: Patchy p => [FilePath] -> FL (TaggedPatch p) C(x y) -> [Tag]+ ct _ NilFL = []+ ct fs (tp:>:tps) = case look_touch fs (tp_patch tp) of+ (True, fs') -> ct fs' tps+ (False, fs') -> tag tp:ct fs' tps+ xs = case get_choices pc of+ fc :> mc :> _ -> ct (map fix files) (fc +>+ mc)++fix :: FilePath -> FilePath+fix f | take 1 (reverse f) == "/" = fix $ reverse $ drop 1 $ reverse f+fix "" = "."+fix "." = "."+fix f = "./" ++ f++choose_touching :: Patchy p => [FilePath] -> FL p C(x y) -> Sealed (FL p C(x))+choose_touching [] p = seal p+choose_touching files p = case get_choices $ select_touching files $ patch_choices p of+ fc :> _ :> _ -> seal $ mapFL_FL tp_patch fc++look_touch :: Patchy p => [FilePath] -> p C(x y) -> (Bool, [FilePath])+look_touch fs p = (any (\tf -> any (affects tf) fs) (list_touched_files p)+ || fs' /= fs, fs')+ where affects touched f | touched == f = True+ affects t f = case splitAt (length f) t of+ (t', '/':_) -> t' == f+ _ -> case splitAt (length t) f of+ (f', '/':_) -> f' == t+ _ -> False+ fs' = sort $ apply_to_filepaths p fs
@@ -0,0 +1,333 @@+-- Copyright (C) 2002-2004 David Roundy+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.++{-# OPTIONS_GHC -cpp -fno-warn-orphans #-}+{-# LANGUAGE CPP #-}++module Darcs.Patch.Viewing ( xml_summary, summarize )+ where++import Prelude hiding ( pi )+import Control.Monad ( liftM )+import Data.List ( sort )++import Darcs.SlurpDirectory ( Slurpy, get_slurp, get_filecontents )+import ByteStringUtils (linesPS )+import qualified Data.ByteString as B (null)+import Darcs.Patch.FileName ( FileName, fp2fn, fn2fp )+import Printer ( Doc, empty, vcat,+ text, blueText, Color(Cyan,Magenta), lineColor,+ minus, plus, ($$), (<+>), (<>),+ prefix, renderString,+ userchunkPS,+ )+import Darcs.Patch.Core ( Patch(..), Named(..),+ patchcontents )+import Darcs.Patch.Prim ( Prim(..), is_hunk, isHunk, formatFileName, showPrim, FileNameFormat(..), Conflict(..),+ Effect, IsConflictedPrim(IsC), ConflictState(..),+ DirPatchType(..), FilePatchType(..) )+import Darcs.Patch.Patchy ( Patchy, Apply, ShowPatch(..), identity )+import Darcs.Patch.Show ( showPatch_, showNamedPrefix )+import Darcs.Patch.Info ( showPatchInfo, human_friendly )+import Darcs.Patch.Apply ( apply_to_slurpy )+#include "impossible.h"+#include "gadts.h"+import Darcs.Ordered ( RL(..), FL(..),+ mapFL, mapFL_FL, reverseRL )++instance ShowPatch Prim where+ showPatch = showPrim OldFormat+ showContextPatch s p@(FP _ (Hunk _ _ _)) = showContextHunk s (PP p)+ showContextPatch s (Split ps) =+ blueText "(" $$ showContextSeries s (mapFL_FL PP ps)+ <> blueText ")"+ showContextPatch _ p = showPatch p+ summary = gen_summary False . (:[]) . IsC Okay+ thing _ = "change"++summarize :: (Conflict e, Effect e) => e C(x y) -> Doc+summarize = gen_summary False . conflictedEffect++instance ShowPatch Patch where+ showPatch = showPatch_+ showContextPatch s (PP x) | is_hunk x = showContextHunk s (PP x)+ showContextPatch _ (ComP NilFL) = blueText "{" $$ blueText "}"+ showContextPatch s (ComP ps) = blueText "{" $$ showContextSeries s ps+ $$ blueText "}"+ showContextPatch _ p = showPatch p+ summary = summarize+ thing _ = "change"++showContextSeries :: (Apply p, ShowPatch p, Effect p) => Slurpy -> FL p C(x y) -> Doc+showContextSeries slur patches = scs slur identity patches+ where scs :: (Apply p, ShowPatch p, Effect p) => Slurpy -> Prim C(w x) -> FL p C(x y) -> Doc+ scs s pold (p:>:ps) =+ case isHunk p of+ Nothing -> showContextPatch s p $$ scs s' identity ps+ Just hp ->+ case ps of+ NilFL -> coolContextHunk s pold hp identity+ (p2:>:_) ->+ case isHunk p2 of+ Nothing -> coolContextHunk s pold hp identity $$ scs s' hp ps+ Just hp2 -> coolContextHunk s pold hp hp2 $$+ scs s' hp ps+ where s' =+ fromJust $ apply_to_slurpy p s+ scs _ _ NilFL = empty++showContextHunk :: (Apply p, ShowPatch p, Effect p) => Slurpy -> p C(x y) -> Doc+showContextHunk s p = case isHunk p of+ Just h -> coolContextHunk s identity h identity+ Nothing -> showPatch p++coolContextHunk :: Slurpy -> Prim C(a b) -> Prim C(b c)+ -> Prim C(c d) -> Doc+coolContextHunk s prev p@(FP f (Hunk l o n)) next =+ case (linesPS . get_filecontents) `liftM` get_slurp f s of+ Nothing -> showPatch p -- This is a weird error...+ Just ls ->+ let numpre = case prev of+ (FP f' (Hunk lprev _ nprev))+ | f' == f &&+ l - (lprev + length nprev + 3) < 3 &&+ lprev < l ->+ max 0 $ l - (lprev + length nprev + 3)+ _ -> if l >= 4 then 3 else l - 1+ pre = take numpre $ drop (l - numpre - 1) ls+ numpost = case next of+ (FP f' (Hunk lnext _ _))+ | f' == f && lnext < l+length n+4 &&+ lnext > l ->+ lnext - (l+length n)+ _ -> 3+ cleanedls = case reverse ls of+ (x:xs) | B.null x -> reverse xs+ _ -> ls+ post = take numpost $ drop (max 0 $ l+length o-1) cleanedls+ in blueText "hunk" <+> formatFileName OldFormat f <+> text (show l)+ $$ prefix " " (vcat $ map userchunkPS pre)+ $$ lineColor Magenta (prefix "-" (vcat $ map userchunkPS o))+ $$ lineColor Cyan (prefix "+" (vcat $ map userchunkPS n))+ $$ prefix " " (vcat $ map userchunkPS post)+coolContextHunk _ _ _ _ = impossible++xml_summary :: (Effect p, Patchy p, Conflict p) => Named p C(x y) -> Doc+xml_summary p = text "<summary>"+ $$ gen_summary True (conflictedEffect $ patchcontents p)+ $$ text "</summary>"++-- Yuck duplicated code below...+escapeXML :: String -> Doc+escapeXML = text . strReplace '\'' "'" . strReplace '"' """ .+ strReplace '>' ">" . strReplace '<' "<" . strReplace '&' "&"++strReplace :: Char -> String -> String -> String+strReplace _ _ [] = []+strReplace x y (z:zs)+ | x == z = y ++ (strReplace x y zs)+ | otherwise = z : (strReplace x y zs)+-- end yuck duplicated code.++gen_summary :: Bool -> [IsConflictedPrim] -> Doc+gen_summary use_xml p+ = vcat themoves+ $$ vcat themods+ where themods = map summ $ combine $ sort $ concatMap s2 p+ s2 :: IsConflictedPrim -> [(FileName, Int, Int, Int, Bool, ConflictState)]+ s2 (IsC c x) = map (append56 c) $ s x+ s :: Prim C(x y) -> [(FileName, Int, Int, Int, Bool)]+ s (FP f (Hunk _ o n)) = [(f, length o, length n, 0, False)]+ s (FP f (Binary _ _)) = [(f, 0, 0, 0, False)]+ s (FP f AddFile) = [(f, -1, 0, 0, False)]+ s (FP f RmFile) = [(f, 0, -1, 0, False)]+ s (FP f (TokReplace _ _ _)) = [(f, 0, 0, 1, False)]+ s (DP d AddDir) = [(d, -1, 0, 0, True)]+ s (DP d RmDir) = [(d, 0, -1, 0, True)]+ s (Split xs) = concat $ mapFL s xs+ s (Move _ _) = [(fp2fn "", 0, 0, 0, False)]+ s (ChangePref _ _ _) = [(fp2fn "", 0, 0, 0, False)]+ s Identity = [(fp2fn "", 0, 0, 0, False)]+ append56 f (a,b,c,d,e) = (a,b,c,d,e,f)+ (-1) .+ _ = -1+ _ .+ (-1) = -1+ a .+ b = a + b+ combine ((f,a,b,r,isd,c):(f',a',b',r',_,c'):ss)+ -- Don't combine AddFile and RmFile: (maybe an old revision of) darcs+ -- allows a single patch to add and remove the same file, see issue 185+ | f == f' && (a /= -1 || b' /= -1) && (a' /= -1 || b /= -1) =+ combine ((f,a.+a',b.+b',r+r',isd,combineConflitStates c c'):ss)+ combine ((f,a,b,r,isd,c):ss) = (f,a,b,r,isd,c) : combine ss+ combine [] = []+ combineConflitStates Conflicted _ = Conflicted+ combineConflitStates _ Conflicted = Conflicted+ combineConflitStates Duplicated _ = Duplicated+ combineConflitStates _ Duplicated = Duplicated+ combineConflitStates Okay Okay = Okay++ summ (f,_,-1,_,False,Okay)+ = if use_xml then text "<remove_file>"+ $$ escapeXML (drop_dotslash $ fn2fp f)+ $$ text "</remove_file>"+ else text "R" <+> text (fn2fp f)+ summ (f,_,-1,_,False,Conflicted)+ = if use_xml then text "<remove_file conflict='true'>"+ $$ escapeXML (drop_dotslash $ fn2fp f)+ $$ text "</remove_file>"+ else text "R!" <+> text (fn2fp f)+ summ (f,_,-1,_,False,Duplicated)+ = if use_xml then text "<remove_file duplicate='true'>"+ $$ escapeXML (drop_dotslash $ fn2fp f)+ $$ text "</remove_file>"+ else text "R" <+> text (fn2fp f) <+> text "(duplicate)"+ summ (f,-1,_,_,False,Okay)+ = if use_xml then text "<add_file>"+ $$ escapeXML (drop_dotslash $ fn2fp f)+ $$ text "</add_file>"+ else text "A" <+> text (fn2fp f)+ summ (f,-1,_,_,False,Conflicted)+ = if use_xml then text "<add_file conflict='true'>"+ $$ escapeXML (drop_dotslash $ fn2fp f)+ $$ text "</add_file>"+ else text "A!" <+> text (fn2fp f)+ summ (f,-1,_,_,False,Duplicated)+ = if use_xml then text "<add_file duplicate='true'>"+ $$ escapeXML (drop_dotslash $ fn2fp f)+ $$ text "</add_file>"+ else text "A" <+> text (fn2fp f) <+> text "(duplicate)"+ summ (f,0,0,0,False,Okay) | f == fp2fn "" = empty+ summ (f,0,0,0,False,Conflicted) | f == fp2fn ""+ = if use_xml then empty -- don't know what to do here...+ else text "!" <+> text (fn2fp f)+ summ (f,0,0,0,False,Duplicated) | f == fp2fn ""+ = if use_xml then empty -- don't know what to do here...+ else text (fn2fp f) <+> text "(duplicate)"+ summ (f,a,b,r,False,Okay)+ = if use_xml then text "<modify_file>"+ $$ escapeXML (drop_dotslash $ fn2fp f)+ <> xrm a <> xad b <> xrp r+ $$ text "</modify_file>"+ else text "M" <+> text (fn2fp f)+ <+> rm a <+> ad b <+> rp r+ summ (f,a,b,r,False,Conflicted)+ = if use_xml then text "<modify_file conflict='true'>"+ $$ escapeXML (drop_dotslash $ fn2fp f)+ <> xrm a <> xad b <> xrp r+ $$ text "</modify_file>"+ else text "M!" <+> text (fn2fp f)+ <+> rm a <+> ad b <+> rp r+ summ (f,a,b,r,False,Duplicated)+ = if use_xml then text "<modify_file duplicate='true'>"+ $$ escapeXML (drop_dotslash $ fn2fp f)+ <> xrm a <> xad b <> xrp r+ $$ text "</modify_file>"+ else text "M" <+> text (fn2fp f)+ <+> rm a <+> ad b <+> rp r <+> text "(duplicate)"+ summ (f,_,-1,_,True,Okay)+ = if use_xml then text "<remove_directory>"+ $$ escapeXML (drop_dotslash $ fn2fp f)+ $$ text "</remove_directory>"+ else text "R" <+> text (fn2fp f) <> text "/"+ summ (f,_,-1,_,True,Conflicted)+ = if use_xml then text "<remove_directory conflict='true'>"+ $$ escapeXML (drop_dotslash $ fn2fp f)+ $$ text "</remove_directory>"+ else text "R!" <+> text (fn2fp f) <> text "/"+ summ (f,_,-1,_,True,Duplicated)+ = if use_xml then text "<remove_directory duplicate='true'>"+ $$ escapeXML (drop_dotslash $ fn2fp f)+ $$ text "</remove_directory>"+ else text "R" <+> text (fn2fp f) <> text "/ (duplicate)"+ summ (f,-1,_,_,True,Okay)+ = if use_xml then text "<add_directory>"+ $$ escapeXML (drop_dotslash $ fn2fp f)+ $$ text "</add_directory>"+ else text "A" <+> text (fn2fp f) <> text "/"+ summ (f,-1,_,_,True,Conflicted)+ = if use_xml then text "<add_directory conflict='true'>"+ $$ escapeXML (drop_dotslash $ fn2fp f)+ $$ text "</add_directory>"+ else text "A!" <+> text (fn2fp f) <> text "/"+ summ (f,-1,_,_,True,Duplicated)+ = if use_xml then text "<add_directory duplicate='true'>"+ $$ escapeXML (drop_dotslash $ fn2fp f)+ $$ text "</add_directory>"+ else text "A!" <+> text (fn2fp f) <> text "/ (duplicate)"+ summ _ = empty+ ad 0 = empty+ ad a = plus <> text (show a)+ xad 0 = empty+ xad a = text "<added_lines num='" <> text (show a) <> text "'/>"+ rm 0 = empty+ rm a = minus <> text (show a)+ xrm 0 = empty+ xrm a = text "<removed_lines num='" <> text (show a) <> text "'/>"+ rp 0 = empty+ rp a = text "r" <> text (show a)+ xrp 0 = empty+ xrp a = text "<replaced_tokens num='" <> text (show a) <> text "'/>"+ drop_dotslash ('.':'/':str) = drop_dotslash str+ drop_dotslash str = str+ themoves :: [Doc]+ themoves = map showmoves p+ showmoves :: IsConflictedPrim -> Doc+ showmoves (IsC _ (Move a b))+ = if use_xml+ then text "<move from=\""+ <> escapeXML (drop_dotslash $ fn2fp a) <> text "\" to=\""+ <> escapeXML (drop_dotslash $ fn2fp b) <> text"\"/>"+ else text " " <> text (fn2fp a)+ <> text " -> " <> text (fn2fp b)+ showmoves _ = empty++instance (Conflict p, ShowPatch p) => ShowPatch (Named p) where+ showPatch (NamedP n [] p) = showPatchInfo n <> showPatch p+ showPatch (NamedP n d p) = showNamedPrefix n d <+> showPatch p+ showContextPatch s (NamedP n [] p) = showPatchInfo n <> showContextPatch s p+ showContextPatch s (NamedP n d p) = showNamedPrefix n d <+> showContextPatch s p+ description (NamedP n _ _) = human_friendly n+ summary p = description p $$ text "" $$+ prefix " " (summarize p) -- this isn't summary because summary does the+ -- wrong thing with (Named (FL p)) so that it can+ -- get the summary of a sequence of named patches+ -- right.+ showNicely p@(NamedP _ _ pt) = description p $$+ prefix " " (showNicely pt)++instance (Conflict p, ShowPatch p) => Show (Named p C(x y)) where+ show = renderString . showPatch++instance (Conflict p, Apply p, Effect p, ShowPatch p) => ShowPatch (FL p) where+ showPatch xs = vcat (mapFL showPatch xs)+ showContextPatch = showContextSeries+ description = vcat . mapFL description+ summary = vcat . mapFL summary+ thing x = thing (helperx x) ++ "s"+ where helperx :: FL a C(x y) -> a C(x y)+ helperx _ = undefined+ things = thing++instance (Conflict p, Apply p, ShowPatch p) => ShowPatch (RL p) where+ showPatch = showPatch . reverseRL+ showContextPatch s = showContextPatch s . reverseRL+ description = description . reverseRL+ summary = summary . reverseRL+ thing = thing . reverseRL+ things = things . reverseRL++instance (Conflict p, Patchy p) => Patchy (FL p)+instance (Conflict p, Patchy p) => Patchy (RL p)
@@ -0,0 +1,144 @@+{-# OPTIONS_GHC -cpp #-}+{-# LANGUAGE CPP #-}++-- Copyright (C) 2003-2004 Jan Scheffczyk and David Roundy+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.++#include "gadts.h"++module Darcs.Population ( Population, patchChanges, applyToPop,+ getPopFrom,+ setPopState,+ DirMark(..),+ getRepoPop, getRepoPopVersion,+ modified_to_xml,+ lookup_pop, lookup_creation_pop,+ ) where++import qualified Data.ByteString.Char8 as BC ( unpack, singleton, pack )+import Data.Maybe ( catMaybes )+import Darcs.Utils ( withCurrentDirectory )++import Darcs.Hopefully ( PatchInfoAnd, hopefully, info )+import Darcs.Patch.FileName ( fn2fp, fp2fn, fn2ps, norm_path )+import Darcs.Patch ( RepoPatch, applyToPop, patchcontents, patchChanges,+ Effect, effect )+import Darcs.Ordered ( FL(..), RL(..), reverseRL, concatRL, mapRL )+import Darcs.Patch.Info ( PatchInfo, idpatchinfo, to_xml )+import Darcs.Patch.Set ( PatchSet )+import Darcs.Sealed ( Sealed(..), seal, unseal )+import Darcs.Repository ( withRepositoryDirectory, ($-), read_repo )+import Darcs.Repository.Pristine ( identifyPristine, getPristinePop )+import Darcs.PopulationData ( Population(..), PopTree(..), Info(..), DirMark(..),+ setPopState, getPopFrom )+import Printer ( empty, text, ($$), (<>), Doc )+import Control.Monad ( liftM )++#include "impossible.h"++-- | population of an empty repository+initPop :: Population+initPop = Pop idpatchinfo (PopDir i [])+ where i = Info {nameI = BC.singleton '.',+ modifiedByI = idpatchinfo,+ modifiedHowI = DullDir,+ createdByI = Nothing,+ creationNameI = Just (BC.singleton '.')}++-- | apply a patchset to a population+applyPatchSetPop :: RepoPatch p => PatchSet p C(x) -> Population -> Population+applyPatchSetPop ps pop = applyPatchesPop (reverseRL $ concatRL ps) pop++-- | apply Patches to a population+applyPatchesPop :: Effect p => FL (PatchInfoAnd p) C(x y) -> Population -> Population+applyPatchesPop NilFL = id+applyPatchesPop (hp:>:hps) = applyPatchesPop hps .+ applyToPop (info hp) (effect $ patchcontents $ hopefully hp)+-- | get the pristine population from a repo+getRepoPop :: FilePath -> IO Population+getRepoPop repobasedir+ = withRepositoryDirectory [] repobasedir $- \repository -> do+ pinfo <- (head . mapRL info . concatRL) `liftM` read_repo repository+ -- pinfo is the latest patchinfo+ mp <- withCurrentDirectory repobasedir $+ identifyPristine >>= getPristinePop pinfo+ case mp of+ (Just pop) -> return pop+ (Nothing) -> getRepoPopVersion repobasedir pinfo++getRepoPopVersion :: FilePath -> PatchInfo -> IO Population+getRepoPopVersion repobasedir pinfo = withRepositoryDirectory [] repobasedir $- \repository ->+ do pips <- concatRL `liftM` read_repo repository+ return $ (unseal applyPatchSetPop) (mkPatchSet $ dropWhileRL ((/=pinfo).info) pips) initPop+ where mkPatchSet (Sealed xs) = seal $ xs :<: NilRL+ dropWhileRL :: (FORALL(x y) a C(x y) -> Bool) -> RL a C(r v) -> Sealed (RL a C(r))+ dropWhileRL _ NilRL = seal NilRL+ dropWhileRL p xs@(x:<:xs')+ | p x = dropWhileRL p xs'+ | otherwise = seal xs++-- Routines for pulling data conveniently out of a Population++lookup_pop :: FilePath -> Population -> Maybe Population+lookup_pop f p = lookup_pop' (BC.unpack $ fn2ps $ fp2fn f) p++lookup_pop' :: String -> Population -> Maybe Population+lookup_pop' f p@(Pop _ (PopFile i))+ | BC.unpack (nameI i) == f = Just p+ | otherwise = Nothing+lookup_pop' d p@(Pop pinfo (PopDir i c))+ | BC.unpack (nameI i) == "." =+ case catMaybes $ map (lookup_pop' (dropDS d).(Pop pinfo)) c of+ [apop] -> Just apop+ [] -> Nothing+ _ -> impossible+ | BC.unpack (nameI i) == takeWhile (/='/') d =+ case dropWhile (=='/') $ dropWhile (/='/') d of+ "" -> Just p+ d' -> case catMaybes $ map (lookup_pop' d'.(Pop pinfo)) c of+ [apop] -> Just apop+ [] -> Nothing+ _ -> impossible+ | otherwise = Nothing+ where dropDS ('.':'/':f) = dropDS f+ dropDS f = f++lookup_creation_pop :: PatchInfo -> FilePath -> Population -> Maybe Population+lookup_creation_pop pinfo f p = lookup_creation_pop' pinfo (BC.unpack $ fn2ps $ fp2fn f) p++lookup_creation_pop' :: PatchInfo -> String -> Population -> Maybe Population+lookup_creation_pop' b a (Pop pinfo pp) = (Pop pinfo) `fmap` lcp pp+ where lcp p@(PopFile i)+ | fixname `fmap` creationNameI i == f && createdByI i == who = Just p+ | otherwise = Nothing+ lcp p@(PopDir i c)+ | fixname `fmap` creationNameI i == f && createdByI i == who = Just p+ | otherwise = case catMaybes $ map lcp c of+ [apop] -> Just apop+ _ -> Nothing+ fixname = BC.pack . fn2fp . norm_path . fp2fn . BC.unpack+ f = Just $ BC.pack $ fn2fp $ norm_path $ fp2fn a+ who = Just b++modified_to_xml :: Info -> Doc+modified_to_xml i | modifiedHowI i == DullDir = empty+ | modifiedHowI i == DullFile = empty+modified_to_xml i = text "<modified>"+ $$ text "<modified_how>" <> text (show (modifiedHowI i)) <>+ text "</modified_how>"+ $$ to_xml (modifiedByI i)+ $$ text "</modified>"
@@ -0,0 +1,123 @@+-- Copyright (C) 2003-2004 Jan Scheffczyk and David Roundy+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.++module Darcs.PopulationData ( Population(..), PopTree(..), Info(..),+ setPopState, notModified, setState,+ DirMark(..), getPopFrom+ ) where+import Darcs.Patch.Info+import Darcs.Utils ( withCurrentDirectory )+import System.Directory ( doesDirectoryExist, getDirectoryContents )+import qualified Data.ByteString.Char8 as BC+import qualified Data.ByteString as B (ByteString)++-- | the population of a darcs repository (simpler Slurpy)+data Population = Pop { popState :: PatchInfo -- ^ the state when last modified+ , popTree :: PopTree -- ^ the directory listing+ }+ deriving (Show, Eq)++setPopState :: PatchInfo -> Population -> Population+setPopState i (Pop _ tr) = Pop i tr++-- | directory listing+data PopTree = PopDir !Info ![PopTree]+ | PopFile !Info+ deriving ( Ord, Eq )++-- | info of a directory member+data DirMark = AddedFile | RemovedFile | MovedFile String+ | ModifiedFile | DullFile+ | AddedDir | RemovedDir | MovedDir !String | DullDir+ deriving ( Ord, Eq )+data Info = Info {nameI :: !B.ByteString, -- ^ name of the element+ modifiedByI :: !PatchInfo, -- ^ last patch modifying this element+ modifiedHowI :: !DirMark, -- ^ how was it modified+ createdByI :: !(Maybe PatchInfo), -- ^ this can be unknown when restored backwards!+ creationNameI :: !(Maybe B.ByteString) -- ^ the original name of the element+ }+ deriving ( Ord, Eq )++-- | was an Info record not modified?+notModified :: Info -> Bool+notModified i = (modifiedHowI i == DullFile) || (modifiedHowI i == DullDir)++-- | set the modifier for an Info record+setState :: Info -> PatchInfo -> Info+setState i pinfo = i { modifiedByI = pinfo }++instance Show PopTree where+ show s = showPop "" s++showPop :: String -> PopTree -> String+showPop indent (PopDir i fs)+ = indent ++ show i ++ "\n" +++ unlines (map (showPop (' ':indent)) fs)+showPop indent (PopFile i)+ = indent ++ show i++instance Show Info where+ show i = show (nameI i) ++ " " ++ show (modifiedHowI i) +++ " at state " ++ show (modifiedByI i)++instance Show DirMark where+ show AddedFile = "File added"+ show RemovedFile = "File removed"+ show (MovedFile s) = "File moved to " ++ s+ show ModifiedFile = "File modified"+ show DullFile = "File old"+ show AddedDir = "Dir added"+ show RemovedDir = "Dir removed"+ show (MovedDir s) = "Dir moved from " ++ s+ show DullDir = "Dir old"++-- | read the population from a given directory @dirname@+-- all folders and documents get the given time @t@+--+-- This needs to be here in order to avoid a circular dependency+-- between Population and Pristine.+getPopFrom :: FilePath -> PatchInfo -> IO Population+getPopFrom the_directory pinfo =+ withCurrentDirectory the_directory $+ do popT <- getPopFrom_helper "."+ return (Pop pinfo popT)+ where getPopFrom_helper :: FilePath -> IO PopTree+ getPopFrom_helper dirname = do+ isdir <- doesDirectoryExist dirname+ let n = BC.pack dirname+ if isdir+ then do+ fnames <- getDirectoryContents dirname+ sl <- withCurrentDirectory dirname+ (sequence $ map getPopFrom_helper $ filter not_hidden fnames)+ let i = Info {nameI = n,+ modifiedByI = pinfo,+ modifiedHowI = DullDir,+ createdByI = Just pinfo,+ creationNameI = Just n}+ return $ PopDir i sl+ else do let i = Info {nameI = n,+ modifiedByI = pinfo,+ modifiedHowI = DullFile,+ createdByI = Just pinfo,+ creationNameI = Just n}+ return $ PopFile i++not_hidden :: FilePath -> Bool+not_hidden ('.':_) = False+not_hidden ('_':_) = False+not_hidden _ = True
@@ -0,0 +1,50 @@+-- Copyright (C) 2003 David Roundy+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.++{-# OPTIONS_GHC -cpp #-}+{-# LANGUAGE CPP #-}++#include "gadts.h"++module Darcs.PrintPatch ( printPatch, contextualPrintPatch,+ printPatchPager, printFriendly ) where++import Darcs.Patch ( Patchy, showContextPatch, showPatch )+import Darcs.SlurpDirectory ( Slurpy )+import Darcs.Arguments ( DarcsFlag, showFriendly )+import Printer ( putDocLnWith )+import Darcs.ColorPrinter ( fancyPrinters )+import Darcs.External ( viewDocWith )++-- | @'printFriendly' opts patch@ prints @patch@ in accordance with the+-- flags in opts, ie, whether @--verbose@ or @--summary@ were passed at+-- the command-line.+printFriendly :: Patchy p => [DarcsFlag] -> p C(x y) -> IO ()+printFriendly opts p = putDocLnWith fancyPrinters $ showFriendly opts p++-- | 'printPatch' prints a patch on standard output.+printPatch :: Patchy p => p C(x y) -> IO ()+printPatch p = putDocLnWith fancyPrinters $ showPatch p++-- | 'printPatchPager' runs '$PAGER' and shows a patch in it.+printPatchPager :: Patchy p => p C(x y) -> IO ()+printPatchPager p = viewDocWith fancyPrinters $ showPatch p++-- | 'contextualPrintPatch' prints a patch, together with its context,+-- on standard output.+contextualPrintPatch :: Patchy p => Slurpy -> p C(x y) -> IO ()+contextualPrintPatch s p = putDocLnWith fancyPrinters $ showContextPatch s p
@@ -0,0 +1,65 @@+{-# LANGUAGE CPP #-}++#include "gadts.h"+module Darcs.ProgressPatches (progressRL, progressFL, progressRLShowTags)+where+import Darcs.Ordered ( FL(..), RL(..), lengthRL, lengthFL )+import Darcs.Hopefully (PatchInfoAnd,info)+import System.IO.Unsafe ( unsafePerformIO )+import Progress (minlist, beginTedious,+ endTedious, progress, progressKeepLatest,+ tediousSize, finishedOne)+import Darcs.Patch.Info (just_name, is_tag)+++-- | Evaluate an 'FL' list and report progress.+progressFL :: String -> FL a C(x y) -> FL a C(x y)+progressFL _ NilFL = NilFL+progressFL k (x:>:xs) = if l < minlist then x:>:xs+ else startit x :>: pl xs+ where l = lengthFL (x:>:xs)+ startit y = unsafePerformIO $ do beginTedious k+ tediousSize k l+ return y+ pl :: FL a C(x y) -> FL a C(x y)+ pl NilFL = NilFL+ pl (y:>:NilFL) = unsafePerformIO $ do endTedious k+ return (y:>:NilFL)+ pl (y:>:ys) = progress k y :>: pl ys++-- | Evaluate an 'RL' list and report progress.+progressRL :: String -> RL a C(x y) -> RL a C(x y)+progressRL _ NilRL = NilRL+progressRL k (x:<:xs) = if l < minlist then x:<:xs+ else startit x :<: pl xs+ where l = lengthRL (x:<:xs)+ startit y = unsafePerformIO $ do beginTedious k+ tediousSize k l+ return y+ pl :: RL a C(x y) -> RL a C(x y)+ pl NilRL = NilRL+ pl (y:<:NilRL) = unsafePerformIO $ do endTedious k+ return (y:<:NilRL)+ pl (y:<:ys) = progress k y :<: pl ys++-- | Evaluate an 'RL' list and report progress. In addition to printing+-- the number of patches we got, show the name of the last tag we got.+progressRLShowTags :: String -> RL (PatchInfoAnd p) C(x y)+ -> RL (PatchInfoAnd p) C(x y)+progressRLShowTags _ NilRL = NilRL+progressRLShowTags k (x:<:xs) = if l < minlist then x:<:xs+ else startit x :<: pl xs+ where l = lengthRL (x:<:xs)+ startit y = unsafePerformIO $ do beginTedious k+ tediousSize k l+ return y+ pl :: RL (PatchInfoAnd p) C(x y) -> RL (PatchInfoAnd p) C(x y)+ pl NilRL = NilRL+ pl (y:<:NilRL) = unsafePerformIO $ do endTedious k+ return (y:<:NilRL)+ pl (y:<:ys) =+ if is_tag iy + then finishedOne k ("back to "++ just_name iy) y :<: pl ys+ else progressKeepLatest k y :<: pl ys+ where+ iy = info y
@@ -0,0 +1,58 @@+-- | This module is used by the push and put commands to apply the a bundle to a+-- remote repository. By remote I do not necessarily mean a repository on another+-- machine, it is just not the repository we're located in.+module Darcs.RemoteApply ( remote_apply, apply_as ) where++import System.Exit ( ExitCode )++import Darcs.Flags ( DarcsFlag( ApplyAs, Debug ) )+import Darcs.Utils ( breakCommand )+import Darcs.URL ( is_url, is_ssh )+import Darcs.External+import Printer++remote_apply :: [DarcsFlag] -> String -> Doc -> IO ExitCode+remote_apply opts repodir bundle + = case apply_as opts of+ Nothing -> if is_ssh repodir+ then apply_via_ssh opts repodir bundle+ else if is_url repodir+ then apply_via_url opts repodir bundle+ else apply_via_local opts repodir bundle+ Just un -> if is_ssh repodir+ then apply_via_ssh_and_sudo repodir un bundle+ else apply_via_sudo un repodir bundle++apply_as :: [DarcsFlag] -> Maybe String+apply_as (ApplyAs user:_) = Just user+apply_as (_:fs) = apply_as fs+apply_as [] = Nothing+apply_via_sudo :: String -> String -> Doc -> IO ExitCode+apply_via_sudo user repo bundle =+ pipeDoc "sudo" ["-u",user,"darcs","apply","--all","--repodir",repo] bundle+apply_via_local :: [DarcsFlag] -> String -> Doc -> IO ExitCode+apply_via_local opts repo bundle =+ pipeDoc "darcs" ("apply":"--all":"--repodir":repo:applyopts opts) bundle++apply_via_url :: [DarcsFlag] -> String -> Doc -> IO ExitCode+apply_via_url opts repo bundle =+ do maybeapply <- maybeURLCmd "APPLY" repo+ case maybeapply of+ Nothing -> apply_via_local opts repo bundle+ Just apply ->+ do let (cmd, args) = breakCommand apply+ pipeDoc cmd (args ++ [repo]) bundle++apply_via_ssh :: [DarcsFlag] -> String -> Doc -> IO ExitCode+apply_via_ssh opts repo bundle =+ pipeDocSSH addr ["darcs apply --all "++unwords (applyopts opts)++" --repodir '"++path++"'"] bundle+ where (addr,':':path) = break (==':') repo++apply_via_ssh_and_sudo :: String -> String -> Doc -> IO ExitCode+apply_via_ssh_and_sudo repo username bundle =+ pipeDocSSH addr ["sudo -u "++username+++ " darcs apply --all --repodir '"++path++"'"] bundle+ where (addr,':':path) = break (==':') repo++applyopts :: [DarcsFlag] -> [String]+applyopts opts = if Debug `elem` opts then ["--debug"] else []
@@ -0,0 +1,251 @@+{-# OPTIONS_GHC -cpp #-}+{-# LANGUAGE CPP #-}++-- Copyright (C) 2007 Eric Kow+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.++-- | Various abstractions for dealing with paths.+module Darcs.RepoPath (+ -- * AbsolutePath+ AbsolutePath,+ makeAbsolute,+ ioAbsolute,+ rootDirectory,+ -- * AbsolutePathOrStd+ AbsolutePathOrStd,+ makeAbsoluteOrStd,+ ioAbsoluteOrStd,+ useAbsoluteOrStd,+ -- * AbsoluteOrRemotePath+ AbsoluteOrRemotePath,+ ioAbsoluteOrRemote,+ isRemote,+ -- * SubPath+ SubPath,+ makeSubPathOf,+ simpleSubPath,+ -- * Miscellaneous+ sp2fn,+ FilePathOrURL(..),+ FilePathLike(toFilePath),+ getCurrentDirectory,+ setCurrentDirectory+) where++import Data.List ( isPrefixOf, isSuffixOf )+import Control.Exception ( bracket )++import Darcs.URL ( is_absolute, is_relative, is_ssh_nopath )+import qualified Workaround ( getCurrentDirectory )+import qualified System.Directory ( setCurrentDirectory )+import System.Directory ( doesDirectoryExist )+import qualified System.FilePath.Posix as FilePath ( normalise )+import qualified System.FilePath as NativeFilePath ( takeFileName, takeDirectory )+import qualified Darcs.Patch.FileName as PatchFileName ( FileName, fp2fn, fn2fp )+#include "impossible.h"++class FilePathOrURL a where+ {-# INLINE toPath #-}+ toPath :: a -> String++class FilePathOrURL a => FilePathLike a where+ {-# INLINE toFilePath #-}+ toFilePath :: a -> FilePath++-- | Paths which are relative to the local darcs repository and normalized.+-- Note: These are understood not to have the dot in front.+newtype SubPath = SubPath FilePath deriving (Eq, Ord)++newtype AbsolutePath = AbsolutePath FilePath deriving (Eq, Ord)++-- | This is for situations where a string (e.g. a command line argument)+-- may take the value \"-\" to mean stdin or stdout (which one depends on+-- context) instead of a normal file path.+data AbsolutePathOrStd = AP AbsolutePath | APStd deriving (Eq, Ord)+data AbsoluteOrRemotePath = AbsP AbsolutePath | RmtP String deriving (Eq, Ord)++instance FilePathOrURL AbsolutePath where+ toPath (AbsolutePath x) = x+instance FilePathOrURL SubPath where+ toPath (SubPath x) = x+instance CharLike c => FilePathOrURL [c] where+ toPath = toFilePath++instance FilePathOrURL AbsoluteOrRemotePath where+ toPath (AbsP a) = toPath a+ toPath (RmtP r) = r++instance FilePathOrURL PatchFileName.FileName where+ toPath = PatchFileName.fn2fp+instance FilePathLike PatchFileName.FileName where+ toFilePath = PatchFileName.fn2fp++instance FilePathLike AbsolutePath where+ toFilePath (AbsolutePath x) = x+instance FilePathLike SubPath where+ toFilePath (SubPath x) = x++class CharLike c where+ toChar :: c -> Char+ fromChar :: Char -> c+instance CharLike Char where+ toChar = id+ fromChar = id++instance CharLike c => FilePathLike [c] where+ toFilePath = map toChar++-- | Make the second path relative to the first, if possible+makeSubPathOf :: AbsolutePath -> AbsolutePath -> Maybe SubPath+makeSubPathOf (AbsolutePath p1) (AbsolutePath p2) =+ -- The slash prevents "foobar" from being treated as relative to "foo"+ if p1 == p2 || (p1 ++ "/") `isPrefixOf` p2+ then Just $ SubPath $ drop (length p1 + 1) p2+ else Nothing++simpleSubPath :: FilePath -> Maybe SubPath+simpleSubPath x | null x = bug "simpleSubPath called with empty path"+ | is_relative x = Just $ SubPath $ FilePath.normalise $ pathToPosix x+ | otherwise = Nothing++-- | Interpret a possibly relative path wrt the current working directory.+ioAbsolute :: FilePath -> IO AbsolutePath+ioAbsolute dir =+ do isdir <- doesDirectoryExist dir+ here <- getCurrentDirectory+ if isdir+ then bracket (setCurrentDirectory dir)+ (const $ setCurrentDirectory $ toFilePath here)+ (const getCurrentDirectory)+ else let super_dir = case NativeFilePath.takeDirectory dir of+ "" -> "."+ d -> d+ file = NativeFilePath.takeFileName dir+ in do abs_dir <- if dir == super_dir+ then return $ AbsolutePath dir+ else ioAbsolute super_dir+ return $ makeAbsolute abs_dir file++-- | Take an absolute path and a string representing a (possibly relative)+-- path and combine them into an absolute path. If the second argument is+-- already absolute, then the first argument gets ignored. This function also+-- takes care that the result is converted to Posix convention and+-- normalized. Also, parent directories (\"..\") at the front of the string+-- argument get canceled out against trailing directory parts of the+-- absolute path argument.+--+-- Regarding the last point, someone more familiar with how these functions+-- are used should verify that this is indeed necessary or at least useful.+makeAbsolute :: AbsolutePath -> FilePath -> AbsolutePath+makeAbsolute a dir = if not (null dir) && is_absolute dir+ then AbsolutePath (norm_slashes dir')+ else ma a dir'+ where+ dir' = FilePath.normalise $ pathToPosix dir+ -- Why do we care to reduce ".." here?+ -- Why not do this throughout the whole path, i.e. "x/y/../z" -> "x/z" ?+ ma here ('.':'.':'/':r) = ma (takeDirectory here) r+ ma here ".." = takeDirectory here+ ma here "." = here+ ma here "" = here+ ma here r = here /- ('/':r)++(/-) :: AbsolutePath -> String -> AbsolutePath+x /- ('/':r) = x /- r+(AbsolutePath "/") /- r = AbsolutePath ('/':simpleClean r)+(AbsolutePath x) /- r = AbsolutePath (x++'/':simpleClean r)++-- | Convert to posix, remove trailing slashes, and (under Posix)+-- reduce multiple leading slashes to one.+simpleClean :: String -> String+simpleClean = norm_slashes . reverse . dropWhile (=='/') . reverse . pathToPosix++-- | The root directory as an absolute path.+rootDirectory :: AbsolutePath+rootDirectory = AbsolutePath "/"++makeAbsoluteOrStd :: AbsolutePath -> String -> AbsolutePathOrStd+makeAbsoluteOrStd _ "-" = APStd+makeAbsoluteOrStd a p = AP $ makeAbsolute a p++ioAbsoluteOrStd :: String -> IO AbsolutePathOrStd+ioAbsoluteOrStd "-" = return APStd+ioAbsoluteOrStd p = AP `fmap` ioAbsolute p++-- | Execute either the first or the second argument action, depending on+-- whether the given path is an 'AbsolutePath' or stdin/stdout.+useAbsoluteOrStd :: (AbsolutePath -> IO a) -> IO a -> AbsolutePathOrStd -> IO a+useAbsoluteOrStd _ f APStd = f+useAbsoluteOrStd f _ (AP x) = f x++ioAbsoluteOrRemote :: String -> IO AbsoluteOrRemotePath+ioAbsoluteOrRemote p = do+ isdir <- doesDirectoryExist p+ if not isdir+ then return $ RmtP $+ case () of _ | is_ssh_nopath p -> p++"."+ | "/" `isSuffixOf` p -> init p+ | otherwise -> p+ else AbsP `fmap` ioAbsolute p++isRemote :: AbsoluteOrRemotePath -> Bool+isRemote (RmtP _) = True+isRemote _ = False++takeDirectory :: AbsolutePath -> AbsolutePath+takeDirectory (AbsolutePath x) =+ case reverse $ drop 1 $ dropWhile (/='/') $ reverse x of+ "" -> AbsolutePath "/"+ x' -> AbsolutePath x'++instance Show AbsolutePath where+ show = show . toFilePath+instance Show SubPath where+ show = show . toFilePath+instance Show AbsolutePathOrStd where+ show (AP a) = show a+ show APStd = "standard input/output"+instance Show AbsoluteOrRemotePath where+ show (AbsP a) = show a+ show (RmtP r) = show r++-- | Normalize the path separator to Posix style (slash, not backslash).+-- This only affects Windows systems.+pathToPosix :: FilePath -> FilePath+pathToPosix = map convert where+#ifdef WIN32+ convert '\\' = '/'+#endif+ convert c = c++-- | Reduce multiple leading slashes to one. This only affects Posix systems.+norm_slashes :: FilePath -> FilePath+#ifndef WIN32+-- multiple slashes in front are ignored under Posix+norm_slashes ('/':p) = '/' : dropWhile (== '/') p+#endif+norm_slashes p = p++getCurrentDirectory :: IO AbsolutePath+getCurrentDirectory = AbsolutePath `fmap` Workaround.getCurrentDirectory++setCurrentDirectory :: FilePathLike p => p -> IO ()+setCurrentDirectory = System.Directory.setCurrentDirectory . toFilePath++{-# INLINE sp2fn #-}+sp2fn :: SubPath -> PatchFileName.FileName+sp2fn = PatchFileName.fp2fn . toFilePath
@@ -0,0 +1,302 @@+-- Copyright (C) 2002-2004 David Roundy+-- Copyright (C) 2005 Juliusz Chroboczek+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.++{-# OPTIONS_GHC -cpp -fglasgow-exts #-}+{-# LANGUAGE CPP, ScopedTypeVariables #-}++#include "gadts.h"++module Darcs.Repository ( Repository, ($-), maybeIdentifyRepository,+ identifyRepositoryFor,+ withRepoLock, withRepoReadLock,+ withRepository, withRepositoryDirectory, withGutsOf,+ makePatchLazy, writePatchSet,+ findRepository, amInRepository, amNotInRepository,+ slurp_pending, replacePristineFromSlurpy,+ slurp_recorded, slurp_recorded_and_unrecorded,+ withRecorded,+ get_unrecorded, get_unrecorded_unsorted, get_unrecorded_no_look_for_adds,+ get_unrecorded_in_files, get_unrecorded_in_files_unsorted,+ read_repo, sync_repo,+ prefsUrl,+ add_to_pending,+ tentativelyAddPatch, tentativelyRemovePatches, tentativelyAddToPending,+ tentativelyReplacePatches,+ tentativelyMergePatches, considerMergeToWorking,+ revertRepositoryChanges, finalizeRepositoryChanges,+ createRepository, copyRepository, copy_oldrepo_patches,+ patchSetToRepository,+ unrevertUrl,+ applyToWorking, patchSetToPatches,+ createPristineDirectoryTree, createPartialsPristineDirectoryTree,+ optimizeInventory, cleanRepository,+ checkPristineAgainstSlurpy, getMarkedupFile,+ PatchSet, SealedPatchSet, PatchInfoAnd,+ setScriptsExecutable,+ checkUnrelatedRepos,+ testTentative, testRecorded+ ) where++import System.Exit ( ExitCode(..), exitWith )++import Darcs.Repository.Internal+ (Repository(..), RepoType(..), ($-), pristineFromWorking,+ maybeIdentifyRepository, identifyRepositoryFor,+ findRepository, amInRepository, amNotInRepository,+ makePatchLazy,+ slurp_pending, replacePristineFromSlurpy,+ slurp_recorded, slurp_recorded_and_unrecorded,+ withRecorded,+ get_unrecorded, get_unrecorded_unsorted, get_unrecorded_no_look_for_adds,+ get_unrecorded_in_files, get_unrecorded_in_files_unsorted,+ read_repo, sync_repo,+ prefsUrl, checkPristineAgainstSlurpy,+ add_to_pending,+ withRepoLock, withRepoReadLock, withRepository, withRepositoryDirectory, withGutsOf,+ tentativelyAddPatch, tentativelyRemovePatches, tentativelyAddToPending,+ tentativelyReplacePatches,+ tentativelyMergePatches, considerMergeToWorking,+ revertRepositoryChanges, finalizeRepositoryChanges,+ unrevertUrl,+ applyToWorking, patchSetToPatches,+ createPristineDirectoryTree, createPartialsPristineDirectoryTree,+ optimizeInventory, cleanRepository,+ getMarkedupFile,+ setScriptsExecutable,+ testTentative, testRecorded+ )+import Darcs.Repository.Cache ( unionCaches, fetchFileUsingCache, HashedDir(..) )+import Darcs.Patch.Set ( PatchSet, SealedPatchSet )++import Control.Monad ( unless, when )+import Data.Either(Either(..))+import System.Directory ( createDirectory )+import System.IO.Error ( isAlreadyExistsError )++import qualified Darcs.Repository.DarcsRepo as DarcsRepo+import qualified Darcs.Repository.HashedRepo as HashedRepo++import Darcs.Hopefully ( PatchInfoAnd, info, extractHash )+import Darcs.Repository.Checkpoint ( identify_checkpoint, write_checkpoint_patch, get_checkpoint )+import Darcs.Repository.ApplyPatches ( apply_patches )+import Darcs.Repository.HashedRepo ( apply_to_tentative_pristine )+import Darcs.Patch ( RepoPatch, Named, Patch, patch2patchinfo, apply )+import Darcs.Ordered ( RL(..), bunchFL, mapFL, mapRL, mapRL_RL, concatFL, reverseRL,+ concatRL, lengthRL, isShorterThanRL )+import Darcs.Patch.Info ( PatchInfo )+import Darcs.Repository.Format ( RepoProperty ( HashedInventory ),+ create_repo_format, format_has, writeRepoFormat )+import Darcs.Repository.Prefs ( write_default_prefs )+import Darcs.Repository.Pristine ( createPristine, flagsToPristine )+import Darcs.Patch.Depends ( get_patches_beyond_tag )+import Darcs.SlurpDirectory ( empty_slurpy )+import Darcs.Utils ( withCurrentDirectory, catchall, promptYorn, prettyError )+import Darcs.External ( copyFileOrUrl, Cachable(..) )+import Progress ( debugMessage, tediousSize,+ beginTedious, endTedious, progress )+import Darcs.ProgressPatches (progressRLShowTags, progressFL)+import Darcs.Lock ( writeBinFile )+import Darcs.Sealed ( Sealed(..), FlippedSeal(..), flipSeal, mapFlipped )++import Darcs.Flags ( DarcsFlag( Quiet, Partial, Lazy, Ephemeral,+ AllowUnrelatedRepos+ ),+ compression )+import Darcs.Global ( darcsdir )+#include "impossible.h"++createRepository :: [DarcsFlag] -> IO ()+createRepository opts = do+ createDirectory darcsdir `catch`+ (\e-> if isAlreadyExistsError e+ then fail "Tree has already been initialized!"+ else fail $ "Error creating directory `"++darcsdir++"'.")+ let rf = create_repo_format opts+ createPristine $ flagsToPristine opts rf+ createDirectory $ darcsdir ++ "/patches"+ createDirectory $ darcsdir ++ "/prefs"+ write_default_prefs+ writeRepoFormat rf (darcsdir++"/format")+ if format_has HashedInventory rf+ then writeBinFile (darcsdir++"/hashed_inventory") ""+ else DarcsRepo.write_inventory "." ((NilRL:<:NilRL) :: PatchSet Patch C(())) -- YUCK!++copyRepository :: RepoPatch p => Repository p C(r u t) -> IO ()+copyRepository fromrepository@(Repo _ opts rf _)+ | Partial `elem` opts && not (format_has HashedInventory rf) =+ do isPartial <- copyPartialRepository fromrepository+ unless (isPartial == IsPartial) $ copyFullRepository fromrepository+ | otherwise = copyFullRepository fromrepository++data PorNP = NotPartial | IsPartial+ deriving ( Eq )++data RepoSort = Hashed | Old++copyInventory :: forall p C(r u t). RepoPatch p => Repository p C(r u t) -> IO ()+copyInventory fromrepo@(Repo fromdir opts rf (DarcsRepository _ cremote)) = do+ repo@(Repo todir xx rf2 (DarcsRepository yy c)) <- identifyRepositoryFor fromrepo "."+ let newrepo :: Repository p C(r u t)+ newrepo = Repo todir xx rf2 (DarcsRepository yy (c `unionCaches` cremote))+ copyHashedHashed = HashedRepo.copy_repo newrepo opts fromdir+ copyAnythingToOld r = withCurrentDirectory todir $ read_repo r >>=+ DarcsRepo.write_inventory_and_patches opts+ repoSort rfx | format_has HashedInventory rfx = Hashed+ | otherwise = Old+ case repoSort rf2 of+ Hashed ->+ if format_has HashedInventory rf+ then copyHashedHashed+ else withCurrentDirectory todir $+ do HashedRepo.revert_tentative_changes+ patches <- read_repo fromrepo+ let k = "Copying patch"+ beginTedious k+ tediousSize k (lengthRL $ concatRL patches)+ let patches' = mapRL_RL (mapRL_RL (progress k)) patches+ HashedRepo.write_tentative_inventory c (compression opts) patches'+ endTedious k+ HashedRepo.finalize_tentative_changes repo (compression opts)+ Old -> case repoSort rf of+ Hashed -> copyAnythingToOld fromrepo+ _ -> copy_oldrepo_patches opts fromrepo todir++copy_oldrepo_patches :: RepoPatch p => [DarcsFlag] -> Repository p C(r u t) -> FilePath -> IO ()+copy_oldrepo_patches opts repository@(Repo dir _ _ _) out = do+ Sealed patches <- DarcsRepo.read_repo opts "." :: IO (SealedPatchSet Patch)+ mpi <- if Partial `elem` opts+ -- FIXME this should get last pinfo *before*+ -- desired tag...+ then identify_checkpoint repository+ else return Nothing+ FlippedSeal scp <- return $ since_checkpoint mpi $ concatRL patches+ DarcsRepo.copy_patches opts dir out $ mapRL info $ scp+ where since_checkpoint :: Maybe PatchInfo+ -> RL (PatchInfoAnd p) C(x y) -> FlippedSeal (RL (PatchInfoAnd p)) C(y)+ since_checkpoint Nothing ps = flipSeal ps+ since_checkpoint (Just ch) (hp:<:ps)+ | ch == info hp = flipSeal $ hp :<: NilRL+ | otherwise = (hp :<:) `mapFlipped` since_checkpoint (Just ch) ps+ since_checkpoint _ NilRL = flipSeal NilRL++copyPartialRepository :: forall p C(r u t). RepoPatch p => Repository p C(r u t) -> IO PorNP+copyPartialRepository fromrepository@(Repo _ opts _ _) = do+ mch <- get_checkpoint fromrepository :: IO (Maybe (Sealed (Named p C(x))))+ case mch of+ Nothing -> do putStrLn "No checkpoint."+ return NotPartial+ Just (Sealed ch) ->+ do copyInventory fromrepository+ withRepoLock opts $- \torepository -> do+ write_checkpoint_patch ch+ local_patches <- read_repo torepository+ let pi_ch = patch2patchinfo ch+ FlippedSeal ps <- return $ get_patches_beyond_tag pi_ch local_patches+ let needed_patches = reverseRL $ concatRL ps+ apply opts ch `catch`+ \e -> fail ("Bad checkpoint!\n" ++ prettyError e)+ apply_patches opts needed_patches+ debugMessage "Writing the pristine"+ pristineFromWorking torepository+ return IsPartial++copyFullRepository :: forall p C(r u t). RepoPatch p => Repository p C(r u t) -> IO ()+copyFullRepository fromrepository@(Repo fromdir opts rffrom _) = do+ copyInventory fromrepository+ debugMessage "Copying prefs"+ copyFileOrUrl opts (fromdir++"/"++darcsdir++"/prefs/prefs") (darcsdir++"/prefs/prefs") (MaxAge 600)+ `catchall` return ()+ debugMessage "Grabbing lock in new repository..."+ withRepoLock opts $- \torepository@(Repo _ _ rfto (DarcsRepository _ c)) ->+ if format_has HashedInventory rffrom && format_has HashedInventory rfto+ then do debugMessage "Writing working directory contents..."+ createPristineDirectoryTree torepository "."+ fetch_patches_if_necessary opts torepository+ when (Partial `elem` opts) $ putStrLn $+ "--partial: hashed or darcs-2 repository detected, using --lazy instead"+ else if format_has HashedInventory rfto+ then do local_patches <- read_repo torepository+ replacePristineFromSlurpy torepository empty_slurpy+ let patchesToApply = progressFL "Applying patch" $ concatFL $ reverseRL $+ mapRL_RL reverseRL local_patches+ sequence_ $ mapFL (apply_to_tentative_pristine c opts) $ bunchFL 100 patchesToApply+ finalizeRepositoryChanges torepository+ debugMessage "Writing working directory contents..."+ createPristineDirectoryTree torepository "."+ else do read_repo torepository >>= (apply_patches opts . reverseRL . concatRL)+ debugMessage "Writing the pristine"+ pristineFromWorking torepository++-- | writePatchSet is like patchSetToRepository, except that it doesn't+-- touch the working directory or pristine cache.+writePatchSet :: RepoPatch p => PatchSet p C(x) -> [DarcsFlag] -> IO (Repository p C(r u t))+writePatchSet patchset opts = do+ maybeRepo <- maybeIdentifyRepository opts "."+ let repo@(Repo _ _ rf2 (DarcsRepository _ c)) = + case maybeRepo of+ Right r -> r+ Left e -> bug ("Current directory not repository in writePatchSet: " ++ e)+ debugMessage "Writing inventory"+ if format_has HashedInventory rf2+ then do HashedRepo.write_tentative_inventory c (compression opts) patchset+ HashedRepo.finalize_tentative_changes repo (compression opts)+ else DarcsRepo.write_inventory_and_patches opts patchset+ return repo++-- | patchSetToRepository takes a patch set, and writes a new repository in the current directory+-- that contains all the patches in the patch set. This function is used when 'darcs get'ing a+-- repository with the --to-match flag and the new repository is not in hashed format.+-- This function does not (yet) work for hashed repositories. If the passed @DarcsFlag@s tell +-- darcs to create a hashed repository, this function will call @error@.+patchSetToRepository :: RepoPatch p => Repository p C(r1 u1 r1) -> PatchSet p C(x)+ -> [DarcsFlag] -> IO (Repository p C(r u t))+patchSetToRepository (Repo fromrepo _ rf _) patchset opts = do+ when (format_has HashedInventory rf) $ -- set up sources and all that+ do writeFile "_darcs/tentative_pristine" "" -- this is hokey+ repox <- writePatchSet patchset opts+ HashedRepo.copy_repo repox opts fromrepo+ repo <- writePatchSet patchset opts+ read_repo repo >>= (apply_patches opts . reverseRL . concatRL)+ debugMessage "Writing the pristine"+ pristineFromWorking repo+ return repo++checkUnrelatedRepos :: [DarcsFlag] -> [PatchInfo] -> PatchSet p C(x) -> PatchSet p C(x) -> IO ()+checkUnrelatedRepos opts common us them+ | AllowUnrelatedRepos `elem` opts || not (null common)+ || concatRL us `isShorterThanRL` 5 || concatRL them `isShorterThanRL` 5+ = return ()+ | otherwise+ = do yorn <- promptYorn ("Repositories seem to be unrelated. Proceed?")+ when (yorn /= 'y') $ do putStrLn "Cancelled."+ exitWith ExitSuccess++-- | Unless a flag has been given in the first argument that tells darcs not to do so (--lazy,+-- --partial or --ephemeral), this function fetches all patches that the given repository has +-- with fetchFileUsingCache. This is used as a helper in copyFullRepository.+fetch_patches_if_necessary :: RepoPatch p => [DarcsFlag] -> Repository p C(r u t) -> IO ()+fetch_patches_if_necessary opts torepository@(Repo _ _ _ (DarcsRepository _ c)) = + unless (Partial `elem` opts || Lazy `elem` opts || Ephemeral `elem` opts) $+ do putInfo "Copying patches, to get lazy repository hit ctrl-C..."+ r <- read_repo torepository+ let peekaboo :: PatchInfoAnd p C(x y) -> IO ()+ peekaboo x = case extractHash x of+ Left _ -> return ()+ Right h -> fetchFileUsingCache c HashedPatchesDir h >> return ()+ sequence_ $ mapRL peekaboo $ progressRLShowTags "Copying patches" $ concatRL r+ where putInfo = when (not $ Quiet `elem` opts) . putStrLn
@@ -0,0 +1,50 @@+-- Copyright (C) 2002-2005,2007 David Roundy+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.++{-# OPTIONS_GHC -cpp #-}+{-# LANGUAGE CPP #-}++#include "gadts.h"++module Darcs.Repository.ApplyPatches ( apply_patches, apply_patches_with_feedback ) where++import Darcs.Patch ( Patchy, apply )+import Darcs.Hopefully ( PatchInfoAnd, hopefully, info )+import Darcs.Patch.Info ( human_friendly )+import Darcs.Ordered ( FL(..), lengthFL, mapFL )+import Darcs.Flags ( DarcsFlag )+import Darcs.Utils ( putDocLnError )+import Progress ( beginTedious, endTedious, tediousSize, finishedOneIO )+import Printer ( text )++apply_patches_with_feedback :: Patchy p => [DarcsFlag] -> String -> FL (PatchInfoAnd p) C(x y) -> IO ()+apply_patches_with_feedback _ _ NilFL = return ()+apply_patches_with_feedback opts k patches =+ do beginTedious k+ tediousSize k (lengthFL patches)+ sequence_ $ mapFL apply_cautiously patches+ endTedious k+ where apply_cautiously :: Patchy p => PatchInfoAnd p C(a b) -> IO ()+ apply_cautiously hp =+ do finishedOneIO k (show $ human_friendly $ info hp)+ apply opts (hopefully hp) `catch` \e ->+ do putDocLnError $ text "Unapplicable patch:"+ putDocLnError $ human_friendly (info hp)+ ioError e++apply_patches :: Patchy p => [DarcsFlag] -> FL (PatchInfoAnd p) C(x y) -> IO ()+apply_patches opts ps = apply_patches_with_feedback opts "Applying patch" ps
@@ -0,0 +1,276 @@+{-# OPTIONS_GHC -cpp -fglasgow-exts #-}+{-# LANGUAGE CPP #-}++#include "gadts.h"++module Darcs.Repository.Cache (+ cacheHash, okayHash, takeHash,+ Cache(..), CacheType(..), CacheLoc(..), WritableOrNot(..),+ HashedDir(..), hashedDir,+ unionCaches, cleanCaches, cleanCachesWithHint,+ fetchFileUsingCache, speculateFileUsingCache, writeFileUsingCache,+ findFileMtimeUsingCache, setFileMtimeUsingCache, peekInCache,+ repo2cache,+ writable, hashedFilePath, allHashedDirs+ ) where++import Control.Monad ( liftM, when, guard )+import Data.List ( nub )+import Data.Maybe ( listToMaybe )+import System.Directory ( removeFile, doesFileExist, getDirectoryContents )+import System.Posix ( setFileTimes )+import System.Posix.Files ( linkCount, modificationTime, getSymbolicLinkStatus )+import System.Posix.Types ( EpochTime )+import System.IO ( hPutStrLn, stderr )++import Crypt.SHA256 ( sha256sum )++import ByteStringUtils ( gzWriteFilePS, linesPS )+import qualified Data.ByteString as B (length, drop, ByteString )+import qualified Data.ByteString.Char8 as BC (unpack)++import SHA1 ( sha1PS )+import System.Posix.Files ( createLink )+import System.Directory ( createDirectoryIfMissing )++import Darcs.External ( gzFetchFilePS, fetchFilePS, speculateFileOrUrl, copyFileOrUrl,+ Cachable( Cachable ) )+import Darcs.Flags ( Compression( .. ) )+import Darcs.Global ( darcsdir )+import Darcs.Lock ( writeAtomicFilePS, gzWriteAtomicFilePS )+import Progress ( progressList, debugMessage, debugFail )+import Darcs.SlurpDirectory ( undefined_time )+import Darcs.URL ( is_file )+import Darcs.Utils ( withCurrentDirectory, catchall )++data HashedDir = HashedPristineDir | HashedPatchesDir | HashedInventoriesDir+hashedDir :: HashedDir -> String+hashedDir HashedPristineDir = "pristine.hashed"+hashedDir HashedPatchesDir = "patches"+hashedDir HashedInventoriesDir = "inventories"++allHashedDirs :: [HashedDir]+allHashedDirs = [HashedPristineDir, HashedPatchesDir, HashedInventoriesDir]++data WritableOrNot = Writable | NotWritable deriving ( Show )+data CacheType = Repo | Directory deriving ( Eq, Show )+data CacheLoc = Cache !CacheType !WritableOrNot !String+newtype Cache = Ca [CacheLoc] -- abstract type for hiding cache++instance Eq CacheLoc where+ (Cache Repo _ a) == (Cache Repo _ b) = a == b+ (Cache Directory _ a) == (Cache Directory _ b) = a == b+ _ == _ = False+instance Show CacheLoc where+ show (Cache Repo Writable a) = "thisrepo:" ++ a+ show (Cache Repo NotWritable a) = "repo:" ++ a+ show (Cache Directory Writable a) = "cache:" ++ a+ show (Cache Directory NotWritable a) = "readonly:" ++ a+instance Show Cache where+ show (Ca cs) = unlines $ map show cs++unionCaches :: Cache -> Cache -> Cache+unionCaches (Ca a) (Ca b) = Ca (nub (a++b))++repo2cache :: String -> Cache+repo2cache r = Ca [Cache Repo NotWritable r]++-- | 'cacheHash' computes the cache hash (i.e. filename) of a packed string.+cacheHash :: B.ByteString -> String+cacheHash ps = case show (B.length ps) of+ x | l > 10 -> sha256sum ps+ | otherwise -> take (10-l) (repeat '0') ++ x ++'-':sha256sum ps+ where l = length x++okayHash :: String -> Bool+okayHash s = length s == 40 || length s == 64 || length s == 75++takeHash :: B.ByteString -> Maybe (String, B.ByteString)+takeHash ps = do h <- listToMaybe $ linesPS ps+ let v = BC.unpack h+ guard $ okayHash v+ Just (v, B.drop (B.length h) ps)++checkHash :: String -> B.ByteString -> Bool+checkHash h s | length h == 40 = sha1PS s == h+ | length h == 64 = sha256sum s == h+ | length h == 75 = B.length s == read (take 10 h) && sha256sum s == drop 11 h+ | otherwise = False+++findFileMtimeUsingCache :: Cache -> HashedDir -> String -> IO EpochTime+findFileMtimeUsingCache (Ca cache) subdir f = mt cache+ where mt [] = return undefined_time+ mt (Cache Repo Writable r:_) = (modificationTime `fmap`+ getSymbolicLinkStatus (r++"/"++darcsdir++"/"++(hashedDir subdir)++"/"++f))+ `catchall` return undefined_time+ mt (_:cs) = mt cs++setFileMtimeUsingCache :: Cache -> HashedDir -> String -> EpochTime -> IO ()+setFileMtimeUsingCache (Ca cache) subdir f t = st cache+ where st [] = return ()+ st (Cache Repo Writable r:_) = setFileTimes (r++"/"++darcsdir++"/"++(hashedDir subdir)++"/"++f) t t+ `catchall` return ()+ st (_:cs) = st cs++fetchFileUsingCache :: Cache -> HashedDir -> String -> IO (String, B.ByteString)+fetchFileUsingCache = fetchFileUsingCachePrivate Anywhere++writable :: CacheLoc -> Bool+writable (Cache _ NotWritable _) = False+writable (Cache _ Writable _) = True++-- | @hashedFilePath cachelocation subdir hash@ returns the physical filename of+-- hash @hash@ in the @subdir@ section of @cachelocation@.+hashedFilePath :: CacheLoc -> HashedDir -> String -> String+hashedFilePath (Cache Directory _ d) s f = d ++ "/" ++ (hashedDir s) ++ "/" ++ f+hashedFilePath (Cache Repo _ r) s f =+ r ++ "/"++darcsdir++"/" ++ (hashedDir s) ++ "/" ++ f++-- | @peekInCache cache subdir hash@ tells whether @cache@ and+-- contains an object with hash @hash@ in a writable position.+-- Florent: why do we want it to be in a writable position?+peekInCache :: Cache -> HashedDir -> String -> IO Bool+peekInCache (Ca cache) subdir f = cacheHasIt cache `catchall` return False+ where cacheHasIt [] = return False+ cacheHasIt (c:cs) | not $ writable c = cacheHasIt cs+ | otherwise = do ex <- doesFileExist $ fn c+ if ex then return True+ else cacheHasIt cs+ fn c = hashedFilePath c subdir f++-- | @speculateFileUsingCache cache subdirectory name@ takes note that+-- the file @name@ is likely to be useful soon: pipelined downloads+-- will add it to the (low-priority) queue, for the rest it is a noop.+speculateFileUsingCache :: Cache -> HashedDir -> String -> IO ()+speculateFileUsingCache c sd h = do debugMessage $ "Speculating on "++h+ copyFileUsingCache OnlySpeculate c sd h++data OrOnlySpeculate = ActuallyCopy | OnlySpeculate deriving ( Eq )++copyFileUsingCache :: OrOnlySpeculate -> Cache -> HashedDir -> String -> IO ()+copyFileUsingCache oos (Ca cache) subdir f =+ do debugMessage $ "I'm doing copyFileUsingCache on "++(hashedDir subdir)++"/"++f+ Just stickItHere <- cacheLoc cache+ createDirectoryIfMissing False (reverse $ dropWhile (/='/') $ reverse stickItHere)+ sfuc cache stickItHere+ `catchall` return ()+ where cacheLoc [] = return Nothing+ cacheLoc (c:cs) | not $ writable c = cacheLoc cs+ | otherwise =+ do ex <- doesFileExist $ fn c+ if ex then fail "Bug in darcs: This exception should be caught in speculateFileUsingCache"+ else do othercache <- cacheLoc cs+ case othercache of Just x -> return $ Just x+ Nothing -> return $ Just (fn c)+ sfuc [] _ = return ()+ sfuc (c:cs) out | not $ writable c =+ if oos == OnlySpeculate+ then speculateFileOrUrl (fn c) out+ else copyFileOrUrl [] (fn c) out Cachable+ | otherwise = sfuc cs out+ fn c = hashedFilePath c subdir f+++data FromWhere = LocalOnly | Anywhere deriving ( Eq )++fetchFileUsingCachePrivate :: FromWhere -> Cache -> HashedDir -> String -> IO (String, B.ByteString)+fetchFileUsingCachePrivate fromWhere (Ca cache) subdir f =+ do when (fromWhere == Anywhere) $ copyFileUsingCache ActuallyCopy (Ca cache) subdir f+ ffuc cache+ `catchall` debugFail ("Couldn't fetch `"++f++"'\nin subdir "++(hashedDir subdir)+++ " from sources:\n\n"++show (Ca cache))+ where ffuc (c:cs)+ | not (writable c) && (Anywhere == fromWhere || is_file (fn c)) =+ do debugMessage $ "In fetchFileUsingCachePrivate I'm going manually"+ debugMessage $ " getting "++f+ debugMessage $ " from " ++ fn c+ x <- gzFetchFilePS (fn c) Cachable+ if not $ checkHash f x+ then do x' <- fetchFilePS (fn c) Cachable+ when (not $ checkHash f x') $+ do hPutStrLn stderr $ "Hash failure in " ++ fn c+ fail $ "Hash failure in " ++ fn c+ return (fn c, x')+ else return (fn c, x) -- FIXME: create links in caches+ `catchall` ffuc cs++ | writable c =+ do x1 <- gzFetchFilePS (fn c) Cachable+ x <- if not $ checkHash f x1+ then do x2 <- fetchFilePS (fn c) Cachable+ when (not $ checkHash f x2) $+ do hPutStrLn stderr $ "Hash failure in " ++ fn c+ removeFile $ fn c+ fail $ "Hash failure in " ++ fn c+ return x2+ else return x1+ mapM_ (tryLinking (fn c)) cs+ return (fn c, x)+ `catchall` do (fname,x) <- ffuc cs+ do createCache c subdir+ createLink fname (fn c)+ return (fn c, x)+ `catchall`+ do gzWriteFilePS (fn c) x `catchall` return ()+ return (fname,x)+ | otherwise = ffuc cs++ ffuc [] = debugFail $ "No sources from which to fetch file `"++f++"'\n"++ show (Ca cache)++ tryLinking ff c@(Cache Directory Writable d) =+ do createDirectoryIfMissing False (d++"/"++(hashedDir subdir))+ createLink ff (fn c)+ `catchall` return ()+ tryLinking _ _ = return ()+ fn c = hashedFilePath c subdir f++createCache :: CacheLoc -> HashedDir -> IO ()+createCache (Cache Directory _ d) subdir =+ createDirectoryIfMissing True (d ++ "/" ++ (hashedDir subdir))+createCache _ _ = return ()++-- | @write compression filename content@ writes @content@ to the file @filename@ according+-- to the policy given by @compression@.+write :: Compression -> String -> B.ByteString -> IO ()+write NoCompression = writeAtomicFilePS+write GzipCompression = gzWriteAtomicFilePS++-- | @writeFileUsingCache cache compression subdir contents@ write the string @contents@ to+-- the directory subdir, except if it is already in the cache, in which case it is a noop.+-- Warning (?) this means that in case of a hash collision, writing using writeFileUsingCache is+-- a noop. The returned value is the filename that was given to the string.+writeFileUsingCache :: Cache -> Compression -> HashedDir -> B.ByteString -> IO String+writeFileUsingCache (Ca cache) compr subdir ps =+ (fetchFileUsingCachePrivate LocalOnly (Ca cache) subdir hash >> return hash) `catchall`+ wfuc cache `catchall`+ debugFail ("Couldn't write `"++hash++"'\nin subdir "++(hashedDir subdir)++" to sources:\n\n"+++ show (Ca cache))+ where hash = cacheHash ps+ wfuc (c:cs) | not $ writable c = wfuc cs+ | otherwise = do createCache c subdir+ write compr (fn c) ps -- FIXME: create links in caches+ return hash+ wfuc [] = debugFail $ "No location to write file `" ++ (hashedDir subdir) ++"/"++hash ++ "'"+ fn c = hashedFilePath c subdir hash++cleanCaches :: Cache -> HashedDir -> IO ()+cleanCaches c d = cleanCachesWithHint' c d Nothing++cleanCachesWithHint :: Cache -> HashedDir -> [String] -> IO ()+cleanCachesWithHint c d h = cleanCachesWithHint' c d (Just h)++cleanCachesWithHint' :: Cache -> HashedDir -> Maybe [String] -> IO ()+cleanCachesWithHint' (Ca cs) subdir hint = mapM_ cleanCache cs+ where cleanCache (Cache Directory Writable d) =+ (withCurrentDirectory (d++"/"++(hashedDir subdir)) $+ do fs' <- getDirectoryContents "."+ let fs = case hint of+ Just h -> h+ Nothing -> fs'+ mapM_ clean $ progressList ("Cleaning cache "++d++"/"++(hashedDir subdir)) $+ filter okayHash fs) `catchall` return ()+ cleanCache _ = return ()+ clean f = do lc <- linkCount `liftM` getSymbolicLinkStatus f+ when (lc < 2) $ removeFile f+ `catchall` return ()
@@ -0,0 +1,174 @@+-- Copyright (C) 2002-2005 David Roundy+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.++{-# OPTIONS_GHC -cpp #-}+{-# LANGUAGE CPP #-}++#include "gadts.h"++module Darcs.Repository.Checkpoint ( get_checkpoint, get_checkpoint_by_default,+ identify_checkpoint,+ write_checkpoint, write_recorded_checkpoint,+ write_checkpoint_patch,+ ) where++import System.Directory ( setCurrentDirectory, createDirectoryIfMissing )+import Workaround ( getCurrentDirectory )+import System.IO.Unsafe ( unsafeInterleaveIO )+import Data.Maybe ( listToMaybe, catMaybes )+import Darcs.Hopefully ( PatchInfoAnd, hopefully, info )+import qualified Data.ByteString as B ( null, empty, ByteString )++import Darcs.Lock ( withTempDir, writeDocBinFile )+import Darcs.SlurpDirectory ( Slurpy, empty_slurpy, mmap_slurp, )+import Darcs.Patch ( RepoPatch, Patch, Named, Prim, invertRL, patch2patchinfo,+ apply_to_slurpy, patchcontents,+ effect, fromPrims,+ is_setpref, infopatch,+ readPatch,+ gzWritePatch+ )+import Darcs.Ordered ( RL(..), FL(..), EqCheck(IsEq,NotEq),+ (+>+), filterFL, unsafeCoerceP,+ mapRL, mapFL_FL, mapRL_RL, reverseRL, concatRL, concatFL )+import Darcs.Repository.Internal ( Repository(..), read_repo, slurp_recorded, withRecorded )+import Darcs.Repository.ApplyPatches ( apply_patches )+import Darcs.Patch.Info ( PatchInfo, make_filename, readPatchInfo,+ showPatchInfo+ )+import Darcs.Diff ( unsafeDiff )+import Darcs.External ( gzFetchFilePS, fetchFilePS, Cachable(..) )+import Darcs.Flags ( DarcsFlag(LookForAdds, Partial, Complete ) )+import Darcs.Patch.Depends ( get_patches_beyond_tag, get_patches_in_tag )+import Darcs.Repository.Prefs ( filetype_function )+import Darcs.Utils ( catchall )+import Darcs.RepoPath ( ioAbsoluteOrRemote, toPath )+import Darcs.Global ( darcsdir )+import Printer ( Doc, ($$), empty )+#include "impossible.h"+import Darcs.Sealed ( Sealed(Sealed), FlippedSeal(..), Sealed2(Sealed2), seal, seal2 )+import Control.Monad ( liftM )++read_patch_ids :: B.ByteString -> [PatchInfo]+read_patch_ids inv | B.null inv = []+read_patch_ids inv = case readPatchInfo inv of+ Just (pinfo,r) -> pinfo : read_patch_ids r+ Nothing -> []++read_checkpoints :: String -> IO [(PatchInfo, Maybe Slurpy)]+read_checkpoints d = do+ realdir <- toPath `fmap` ioAbsoluteOrRemote d+ pistr <- fetchFilePS (realdir++"/"++darcsdir++"/checkpoints/inventory") Uncachable+ `catchall` return B.empty+ pis <- return $ reverse $ read_patch_ids pistr+ slurpies <- sequence $ map (fetch_checkpoint realdir) pis+ return $ zip pis slurpies+ where fetch_checkpoint r pinfo =+ unsafeInterleaveIO $ do+ pstr <- gzFetchFilePS+ (r++"/"++darcsdir++"/checkpoints/"++make_filename pinfo) Cachable+ case fst `fmap` (readPatch pstr :: Maybe (Sealed (Named Patch C(x)), B.ByteString)) of+ Nothing -> return Nothing+ Just (Sealed p) -> return $ apply_to_slurpy p empty_slurpy++get_checkpoint :: RepoPatch p => Repository p C(r u t) -> IO (Maybe (Sealed (Named p C(x))))+get_checkpoint repository@(Repo _ opts _ _) = if Partial `elem` opts+ then get_check_internal repository+ else return Nothing++get_checkpoint_by_default :: RepoPatch p => Repository p C(r u t) -> IO (Maybe (Sealed (Named p C(x))))+get_checkpoint_by_default repository@(Repo _ opts _ _) = if Complete `elem` opts+ then return Nothing+ else get_check_internal repository++identify_checkpoint :: RepoPatch p => Repository p C(r u t) -> IO (Maybe PatchInfo)+identify_checkpoint repository@(Repo r _ _ _) = do+ pis <- (map sp2i . catMaybes . mapRL lastRL) `liftM` read_repo repository+ pistr <- fetchFilePS (r++"/"++darcsdir++"/checkpoints/inventory") Uncachable+ `catchall` return B.empty+ return $ listToMaybe $ filter (`elem` pis) $ reverse $ read_patch_ids pistr+ where lastRL :: RL a C(x y) -> Maybe (Sealed2 a)+ lastRL as = do Sealed ps <- headFL (reverseRL as)+ return $ seal2 ps+ headFL :: FL a C(x y) -> Maybe (Sealed (a C(x)))+ headFL (x:>:_) = Just $ seal x+ headFL NilFL = Nothing+ sp2i :: Sealed2 (PatchInfoAnd p) -> PatchInfo+ sp2i (Sealed2 p) = info p++get_check_internal :: RepoPatch p => Repository p C(r u t) -> IO (Maybe (Sealed (Named p C(x))))+get_check_internal repository@(Repo r _ _ _) = do+ mc <- identify_checkpoint repository+ case mc of+ Nothing -> return Nothing+ Just pinfo -> do ps <- gzFetchFilePS+ (r++"/"++darcsdir++"/checkpoints/"++make_filename pinfo) Cachable+ return $ case readPatch ps of+ Just (p, _) -> Just p+ Nothing -> Nothing++format_inv :: [PatchInfo] -> Doc+format_inv [] = empty+format_inv (pinfo:ps) = showPatchInfo pinfo+ $$ format_inv ps++write_recorded_checkpoint :: RepoPatch p => Repository p C(r u t) -> PatchInfo -> IO ()+write_recorded_checkpoint r@(Repo _ _ _ _) pinfo = do+ Sealed ps <- (seal . mapFL_FL hopefully.reverseRL.concatRL) `liftM` read_repo r+ ftf <- filetype_function+ s <- slurp_recorded r+ write_checkpoint_patch $ infopatch pinfo+ (fromPrims $ changepps ps +>+ unsafeDiff [LookForAdds] ftf empty_slurpy s :: Patch C(() y))+ where changeps = filterFL is_setprefFL .+ effect . patchcontents+ changepps = concatFL . mapFL_FL changeps++is_setprefFL :: Prim C(x y) -> EqCheck C(x y)+is_setprefFL p | is_setpref p = NotEq+ | otherwise = unsafeCoerceP IsEq++write_checkpoint :: RepoPatch p => Repository p C(r u t) -> PatchInfo -> IO ()+write_checkpoint repo@(Repo _ _ _ _) pinfo = do+ repodir <- getCurrentDirectory+ Sealed pit <- get_patches_in_tag pinfo `liftM` read_repo repo+ let ps = (reverseRL.mapRL_RL hopefully.concatRL) pit+ ftf <- filetype_function+ with_tag repo pinfo $ do+ s <- mmap_slurp "."+ setCurrentDirectory repodir+ write_checkpoint_patch $ infopatch pinfo $+ (fromPrims $ changepps ps +>+ unsafeDiff [LookForAdds] ftf empty_slurpy s :: Patch C(() y))+ where changeps = filterFL is_setprefFL .+ effect . patchcontents+ changepps = concatFL . mapFL_FL changeps++write_checkpoint_patch :: RepoPatch p => Named p C(x y) -> IO ()+write_checkpoint_patch p =+ do createDirectoryIfMissing False (darcsdir++"/checkpoints")+ gzWritePatch (darcsdir++"/checkpoints/"++make_filename (patch2patchinfo p)) p+ cpi <- (map fst) `fmap` read_checkpoints "."+ writeDocBinFile (darcsdir++"/checkpoints/inventory")+ $ format_inv $ reverse $ patch2patchinfo p:cpi++with_tag :: RepoPatch p => Repository p C(r u t) -> PatchInfo -> (IO ()) -> IO ()+with_tag r pinfo job = do+ ps <- read_repo r+ case get_patches_beyond_tag pinfo ps of+ FlippedSeal (extras :<: NilRL) -> withRecorded r (withTempDir "checkpoint") $ \_ -> do+ apply_patches [] $ invertRL extras+ job+ _ -> bug "with_tag"
@@ -0,0 +1,344 @@+% Copyright (C) 2002-2005,2007-2008 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\chapter{DarcsRepo format}++A repository consists of a working directory, which has within it a+directory called \verb!_darcs!. There must also be a subdirectory within+\verb!_darcs! named \verb!patches!. The \verb!patches! directory contains+the actual patches which are in the repository. There must also be a+\emph{pristine tree}, which may either be a directory containing a cache of+the version of the tree which has been recorded, or a stub, and may be+named either ``current'' or ``pristine''.++\emph{WARNING!} Viewing files in the pristine cache is perfectly+acceptable, but if you view them with an editor (e.g.\ vi or Emacs), that+editor may create temporary files in the pristine tree+(\verb|_darcs/pristine/| or \verb|_darcs/current/|), which will temporarily+cause your repository to be inconsistent. So \emph{don't record any+patches while viewing files in \_darcs/current with an editor!} A better+plan would be to restrict yourself to viewing these files with a pager such+as more or less.++Also within \verb!_darcs! is the \verb!inventory! file, which lists all the+patches that are in the repository. Moreover, it also gives the order of the+representation of the patches as they are stored. Given a source of patches,+i.e.\ any other set of repositories which have between them all the patches+contained in a given repository, that repository can be reproduced based on only the+information in the \verb!inventory! file. Under those circumstances, the+order of the patches specified in the \verb!inventory! file would be+unimportant, as this order is only needed to provide context for the+interpretation of the stored patches in this repository.++\begin{code}+{-# OPTIONS_GHC -cpp -fglasgow-exts #-}+{-# LANGUAGE CPP, ScopedTypeVariables #-}++#include "gadts.h"++module Darcs.Repository.DarcsRepo ( write_inventory, write_inventory_and_patches,+ add_to_inventory, add_to_tentative_pristine,+ add_to_tentative_inventory, remove_from_tentative_inventory,+ finalize_tentative_changes, finalize_pristine_changes,+ revert_tentative_changes,+ read_repo, read_tentative_repo, write_and_read_patch,+ copy_patches+ ) where++import System.Directory ( doesDirectoryExist, createDirectoryIfMissing )+import Workaround ( renameFile )+import Darcs.Utils ( clarify_errors )+import Progress ( debugMessage, beginTedious, endTedious, finishedOneIO )+import Darcs.RepoPath ( ioAbsoluteOrRemote, toPath )+import System.IO ( hPutStrLn, stderr )+import System.IO.Unsafe ( unsafeInterleaveIO )+import Control.Monad ( liftM, when, unless )+import Darcs.Hopefully ( Hopefully, PatchInfoAnd,+ patchInfoAndPatch, info,+ actually, hopefully, unavailable, n2pia )+import Darcs.SignalHandler ( withSignalsBlocked )++import ByteStringUtils ( gzReadFilePS )+import qualified Data.ByteString as B (ByteString, null, readFile, empty)+import qualified Data.ByteString.Char8 as BC (break, pack)++import Darcs.SlurpDirectory ( Slurpy, empty_slurpy )+import Darcs.Patch ( RepoPatch, Effect, Prim, Named, Patch, invert,+ effect,+ patch2patchinfo,+ apply_to_slurpy,+ readPatch,+ writePatch, gzWritePatch, showPatch )+import Darcs.Ordered ( FL(..), RL(..), (:<)(..),+ reverseFL, mapFL, unsafeCoerceP,+ reverseRL, concatRL, mapRL, mapRL_RL )+import Darcs.Patch.Info ( PatchInfo, make_filename, readPatchInfo,+ showPatchInfo, is_tag+ )+import Darcs.Patch.Set ( PatchSet, SealedPatchSet )+import Darcs.External ( gzFetchFilePS, fetchFilePS, copyFilesOrUrls, Cachable(..),+ cloneFile )+import Darcs.Lock ( writeBinFile, writeDocBinFile, appendDocBinFile, appendBinFile )+import Darcs.Flags ( DarcsFlag( NoCompress ) )+import Darcs.Patch.Depends ( slightly_optimize_patchset, commute_to_end, deep_optimize_patchset )+import Darcs.Repository.Pristine ( identifyPristine, applyPristine )+import Darcs.Global ( darcsdir )+import Darcs.Utils ( catchall )+import Darcs.ProgressPatches ( progressFL )+import Printer ( text, (<>), Doc, ($$), empty )+import Darcs.Sealed ( Sealed(Sealed), seal, unseal )+\end{code}++There is a very special patch which may be stored in \verb!patches! which+is called `pending'. This patch describes any changes which have not yet+been recorded, and cannot be determined by a simple diff. For example, file+additions or renames are placed in pending until they are recorded.+Similarly, token replaces are stored in pending until they are recorded.++\begin{code}+write_patch :: RepoPatch p => [DarcsFlag] -> Named p C(x y) -> IO FilePath+write_patch opts p =+ do let writeFun = if NoCompress `elem` opts+ then writePatch+ else gzWritePatch+ pname = darcsdir++"/patches/"++make_filename (patch2patchinfo p)+ writeFun pname p+ return pname++write_and_read_patch :: RepoPatch p => [DarcsFlag] -> PatchInfoAnd p C(x y)+ -> IO (PatchInfoAnd p C(x y))+write_and_read_patch opts p = do fn <- write_patch opts $ hopefully p+ unsafeInterleaveIO $ parse fn+ where parse fn = do debugMessage ("Reading patch file: "++ fn)+ ps <- gzReadFilePS fn+ Sealed pp <- case readPatch ps of+ Just (x,_) -> return x+ Nothing -> fail ("Couldn't parse patch file "++fn)+ return $ n2pia $ unsafeCoerceP pp++--format_inventory is not exported for use outside of the DarcsRepo module+--itself.+format_inventory :: [PatchInfo] -> Doc+format_inventory [] = empty+format_inventory (pinfo:ps) = showPatchInfo pinfo $$ format_inventory ps++write_inventory :: RepoPatch p => FilePath -> PatchSet p C(x) -> IO ()+-- Note that write_inventory optimizes the inventory it writes out by+-- checking on tag dependencies.+-- FIXME: There is also a problem that write_inventory always writes+-- out the entire inventory, including the parts that you haven't+-- changed...+write_inventory dir ps = withSignalsBlocked $ do+ createDirectoryIfMissing False (dir++"/"++darcsdir++"/inventories")+ simply_write_inventory "inventory" dir $ slightly_optimize_patchset ps++simply_write_inventory :: RepoPatch p => String -> FilePath -> PatchSet p C(x) -> IO ()+simply_write_inventory name dir NilRL =+ writeBinFile (dir++"/"++darcsdir++"/"++name) ""+simply_write_inventory name dir (ps:<:NilRL) = do+ writeDocBinFile (dir++"/"++darcsdir++"/"++name) $ format_inventory $ mapFL info $ reverseRL ps+simply_write_inventory _ _ (NilRL:<:_) =+ fail $ "Bug in simply_write_inventory, please report!"+simply_write_inventory name dir (ps:<:pss) = do+ tagname <- return $ make_filename $ last $ mapRL info ps+ simply_write_inventory ("inventories/"++tagname) dir pss+ writeDocBinFile (dir++"/"++darcsdir++"/"++name) $ text "Starting with tag:"+ $$ format_inventory (mapFL info $ reverseRL ps)++write_inventory_and_patches :: RepoPatch p => [DarcsFlag] -> PatchSet p C(x) -> IO ()+write_inventory_and_patches opts ps = do write_inventory "." ps+ sequence_ $ mapRL (write_patch opts . hopefully) $ concatRL ps++add_to_inventory :: FilePath -> [PatchInfo] -> IO ()+add_to_inventory dir pinfos =+ appendDocBinFile (dir++"/"++darcsdir++"/inventory") $ text "\n" <> pidocs pinfos+ where+ pidocs [] = text ""+ pidocs (p:ps) = showPatchInfo p $$ pidocs ps++add_to_tentative_inventory :: forall p C(x y). RepoPatch p => [DarcsFlag] -> Named p C(x y) -> IO FilePath+add_to_tentative_inventory opts p =+ do appendDocBinFile (darcsdir++"/tentative_inventory") $ text "\n"+ <> showPatchInfo (patch2patchinfo p)+ when (is_tag $ patch2patchinfo p) $+ do debugMessage "Optimizing the tentative inventory, since we're adding a tag."+ realdir <- toPath `fmap` ioAbsoluteOrRemote "."+ let k = "Reading tentative inventory"+ beginTedious k+ Sealed ps <- read_repo_private k opts realdir "tentative_inventory"+ :: IO (SealedPatchSet p)+ simply_write_inventory "tentative_inventory" "." $ slightly_optimize_patchset ps+ write_patch opts p++add_to_tentative_pristine :: Effect p => p C(x y) -> IO ()+add_to_tentative_pristine p =+ do -- Sealed p <- (fst . fromJust . readPatchCarefully) `fmap` gzReadFilePS fp+ appendDocBinFile (darcsdir++"/tentative_pristine") $ showPatch (effect p) -- FIXME: this is inefficient!+ appendBinFile (darcsdir++"/tentative_pristine") "\n"++remove_from_tentative_inventory :: RepoPatch p => Bool -> [DarcsFlag] -> FL (Named p) C(x y) -> IO ()+remove_from_tentative_inventory update_pristine opts to_remove =+ do finalize_tentative_changes+ Sealed allpatches <- read_repo opts "."+ skipped :< unmodified <- return $ commute_to_end (unsafeCoerceP to_remove) allpatches+ sequence_ $ mapFL (write_patch opts) skipped+ write_inventory "." $ deep_optimize_patchset+ $ mapRL_RL n2pia (reverseFL skipped) :<: unmodified+ remove_from_checkpoint_inventory to_remove+ when update_pristine $+ do pris <- identifyPristine+ repairable $ applyPristine pris+ $ progressFL "Applying inverse to pristine" $ invert to_remove+ revert_tentative_changes++finalize_tentative_changes :: IO ()+finalize_tentative_changes = renameFile (darcsdir++"/tentative_inventory") (darcsdir++"/inventory")++finalize_pristine_changes :: IO ()+finalize_pristine_changes =+ do Sealed ps <- read_patches $ darcsdir++"/tentative_pristine"+ pris <- identifyPristine+ repairable $ applyPristine pris ps+ where + read_patches :: String -> IO (Sealed (FL Prim C(x)))+ read_patches f = do ps <- B.readFile f+ return $ case readPatch ps of+ Just (x, _) -> x+ Nothing -> seal $ NilFL++repairable :: IO a -> IO a+repairable x = x `clarify_errors` unlines+ ["Your repository is now in an inconsistent state.",+ "This must be fixed by running darcs repair."]++revert_tentative_changes :: IO ()+revert_tentative_changes =+ do cloneFile (darcsdir++"/inventory") (darcsdir++"/tentative_inventory")+ writeBinFile (darcsdir++"/tentative_pristine") ""++copy_patches :: [DarcsFlag] -> FilePath -> FilePath -> [PatchInfo] -> IO ()+copy_patches opts dir out patches = do+ realdir <- toPath `fmap` ioAbsoluteOrRemote dir+ copyFilesOrUrls opts (realdir++"/"++darcsdir++"/patches") (map make_filename patches)+ (out++"/"++darcsdir++"/patches") Cachable++read_repo :: RepoPatch p => [DarcsFlag] -> String -> IO (SealedPatchSet p)+read_repo opts d = do+ realdir <- toPath `fmap` ioAbsoluteOrRemote d+ let k = "Reading inventory of repository "++d+ beginTedious k+ read_repo_private k opts realdir "inventory" `catch`+ (\e -> do hPutStrLn stderr ("Invalid repository: " ++ realdir)+ ioError e)++read_tentative_repo :: RepoPatch p => [DarcsFlag] -> String -> IO (SealedPatchSet p)+read_tentative_repo opts d = do+ realdir <- toPath `fmap` ioAbsoluteOrRemote d+ let k = "Reading tentative inventory of repository "++d+ beginTedious k+ read_repo_private k opts realdir "tentative_inventory" `catch`+ (\e -> do hPutStrLn stderr ("Invalid repository: " ++ realdir)+ ioError e)++read_repo_private :: RepoPatch p => String -> [DarcsFlag] -> FilePath -> FilePath -> IO (SealedPatchSet p)+read_repo_private k opts d iname = do+ i <- gzFetchFilePS (d++"/"++darcsdir++"/"++iname) Uncachable+ finishedOneIO k iname+ (rest,str) <- case BC.break ((==) '\n') i of+ (swt,pistr) | swt == BC.pack "Starting with tag:" ->+ do r <- rr $ head $ read_patch_ids pistr+ return (r,pistr)+ _ -> do endTedious k+ return (seal NilRL,i)+ pis <- return $ reverse $ read_patch_ids str+ isdir <- doesDirectoryExist d+ let parse f = let fn = d ++ "/"++darcsdir++"/patches/" ++ make_filename f+ in if isdir then parse_local fn+ else parse_remote fn+ lift2Sealed (:<:) (return rest) (read_patches parse pis)+ where rr pinfo = unsafeInterleaveIO $ read_repo_private k opts d $+ "inventories/"++make_filename pinfo+ -- parse_remote should really download to a temporary file removed+ -- at exit+ parse_remote, parse_local :: RepoPatch p => String -> IO (Sealed (Hopefully (Named p) C(x)))+ parse_remote fn = do ps <- gzFetchFilePS fn Cachable+ return $ hopefullyNoParseError fn (readPatch ps)+ parse_local fn = do ps <- gzReadFilePS fn+ return $ hopefullyNoParseError fn (readPatch ps)+ hopefullyNoParseError :: String -> Maybe (Sealed (a C(x)), b) -> Sealed (Hopefully a C(x))+ hopefullyNoParseError _ (Just (Sealed x, _)) = seal $ actually x+ hopefullyNoParseError s Nothing = seal $ unavailable $ "Couldn't parse file "++s+ read_patches :: RepoPatch p => (FORALL(b) PatchInfo -> IO (Sealed (Hopefully (Named p) C(b))))+ -> [PatchInfo] -> IO (Sealed (RL (PatchInfoAnd p) C(x)))+ read_patches _ [] = return $ seal NilRL+ read_patches parse (i:is) =+ lift2Sealed (\p rest -> i `patchInfoAndPatch` p :<: rest)+ (read_patches parse is)+ (parse i `catch` \e -> return $ seal $ unavailable $ show e)+ lift2Sealed :: (FORALL(y z) q C(y z) -> p C(x y) -> r C(x z))+ -> IO (Sealed (p C(x))) -> (FORALL(b) IO (Sealed (q C(b)))) -> IO (Sealed (r C(x)))+ lift2Sealed f iox ioy = do Sealed x <- unseal seal `fmap` unsafeInterleaveIO iox+ Sealed y <- unseal seal `fmap` unsafeInterleaveIO ioy+ return $ seal $ f y x++read_patch_ids :: B.ByteString -> [PatchInfo]+read_patch_ids inv | B.null inv = []+read_patch_ids inv = case readPatchInfo inv of+ Just (pinfo,r) -> pinfo : read_patch_ids r+ Nothing -> []++read_checkpoints :: String -> IO [(PatchInfo, Maybe Slurpy)]+read_checkpoints d = do+ realdir <- toPath `fmap` ioAbsoluteOrRemote d+ pistr <- fetchFilePS (realdir++"/"++darcsdir++"/checkpoints/inventory") Uncachable+ `catchall` return B.empty+ pis <- return $ reverse $ read_patch_ids pistr+ slurpies <- sequence $ map (fetch_checkpoint realdir) pis+ return $ zip pis slurpies+ where fetch_checkpoint r pinfo =+ unsafeInterleaveIO $ do+ pstr <- gzFetchFilePS+ (r++"/"++darcsdir++"/checkpoints/"++make_filename pinfo) Cachable+ case fst `liftM` readPatch_ pstr of+ Nothing -> return Nothing+ Just (Sealed p) -> return $ apply_to_slurpy p empty_slurpy+ readPatch_ :: B.ByteString -> Maybe (Sealed (Named Patch C(x)), B.ByteString)+ readPatch_ = readPatch++remove_from_checkpoint_inventory :: RepoPatch p => FL (Named p) C(x y) -> IO ()+remove_from_checkpoint_inventory ps = do+ -- only tags can be checkpoints+ let pinfos = filter is_tag $ mapFL patch2patchinfo ps+ unless (null pinfos) $ do+ createDirectoryIfMissing False (darcsdir++"/checkpoints")+ cpi <- (map fst) `liftM` read_checkpoints "."+ writeDocBinFile (darcsdir++"/checkpoints/inventory") $+ format_inventory $ reverse $ filter (`notElem` pinfos) cpi+\end{code}++The \verb!_darcs! directory also contains a directory called+``\verb!prefs!'', which is described in Chapter~\ref{configuring}.++\begin{comment}+\section{Getting interesting info on change history}++One can query the repository for the entire markup history of a file. This+provides a data structure which contains a history of \emph{all} the+revisions ever made on a given file.++\end{comment}+
@@ -0,0 +1,153 @@+-- Copyright (C) 2005 David Roundy+--+-- This file is licensed under the GPL, version two or later.++{-# OPTIONS_GHC -cpp #-}+{-# LANGUAGE CPP #-}++module Darcs.Repository.Format ( RepoFormat(..), RepoProperty(..), identifyRepoFormat,+ create_repo_format, writeRepoFormat,+ write_problem, read_problem, readfrom_and_writeto_problem,+ format_has, format_has_together,+ ) where++import Data.List ( sort )+import Data.Maybe ( isJust, catMaybes )+import Control.Monad ( msum )++import Darcs.SignalHandler ( catchNonSignal )+import Darcs.External ( fetchFilePS, Cachable( Cachable ) )+import Darcs.Flags ( DarcsFlag ( UseFormat2, UseHashedInventory,+ UseOldFashionedInventory ) )+import Darcs.Lock ( writeBinFile )+import Darcs.Utils ( catchall, prettyException )+import Progress ( beginTedious, endTedious, finishedOneIO )+import Darcs.Global ( darcsdir )++import ByteStringUtils ( linesPS )+import qualified Data.ByteString.Char8 as BC (split, unpack, singleton, elemIndex, pack)+import qualified Data.ByteString as B (ByteString, null, empty)+import qualified ByteStringUtils as BU ( intercalate )++#include "impossible.h"++data RepoProperty = Darcs1_0 | Darcs2 | HashedInventory++-- | @RepoFormat@ is the representation of the format of a+-- repository. Each sublist corresponds to a line in the format+-- file. Each line is decomposed into words.+newtype RepoFormat = RF [[B.ByteString]] deriving ( Show )++-- | The file where the format information should be.+df :: FilePath+df = darcsdir++"/format"++-- | @identifyRepoFormat URL@ identifies the format of the repository+-- at the given address. Return @Left reason@ if it fails, where+-- @reason@ explains why we weren't able to identify the format.+identifyRepoFormat :: String -> IO (Either String RepoFormat)+identifyRepoFormat repo =+ do let k = "Identifying repository "++repo+ beginTedious k+ finishedOneIO k "format"+ dff <- fetchFilePS (repo ++ "/" ++ df) Cachable `catchall` return B.empty+ -- below is a workaround for servers that don't return a 404 on nonexistent files+ rf <- if B.null dff || isJust (BC.elemIndex '<' dff)+ then do finishedOneIO k "inventory"+ have_inventory <- doesRemoteFileExist (repo++"/"++darcsdir++"/inventory")+ case have_inventory of+ Right _ -> return $ Right default_repo_format+ Left e -> return $ Left $ "Not a repository: "++repo++" ("++e++")"+ else return $ Right $ parse_repo_format dff+ endTedious k+ return rf+ where drfe x = fetchFilePS x Cachable >> return True+ doesRemoteFileExist x = (fmap Right) (drfe x) `catchNonSignal`+ (\e -> return (Left (prettyException e)))++-- | @writeRepoFormat@ writes the repo format to the given file.+writeRepoFormat :: RepoFormat -> FilePath -> IO ()+writeRepoFormat (RF rf) loc = writeBinFile loc $ unlines $+ map (BC.unpack . BU.intercalate (BC.singleton '|')) rf++parse_repo_format :: B.ByteString -> RepoFormat+parse_repo_format ps =+ RF $ map (BC.split '|') $ filter (not . B.null) $ linesPS ps++-- | The repo format we assume if we do not find a format file.+default_repo_format :: RepoFormat+default_repo_format = RF [[rp2ps Darcs1_0]]++create_repo_format :: [DarcsFlag] -> RepoFormat+create_repo_format fs = RF ([map rp2ps flags2inv] ++ maybe2)+ where flags2inv | UseFormat2 `elem` fs = [HashedInventory]+ | UseHashedInventory `elem` fs = [HashedInventory]+ | UseOldFashionedInventory `elem` fs = [Darcs1_0]+ | otherwise = [HashedInventory]+ maybe2 = if UseFormat2 `notElem` fs &&+ (UseOldFashionedInventory `elem` fs ||+ UseHashedInventory `elem` fs)+ then []+ else [[rp2ps Darcs2]]++-- | @write_problem from@ tells if we can write to a repo in format @form@.+-- it returns @Nothing@ if there's no problem writing to such a repository.+write_problem :: RepoFormat -> Maybe String+write_problem rf | isJust $ read_problem rf = read_problem rf+write_problem (RF ks) = unlines `fmap` justsOrNothing (map wp ks)+ where wp x | all is_known x = Nothing+ wp [] = impossible+ wp x = Just $ unwords $ "Can't write repository format: " :+ map BC.unpack (filter (not . is_known) x)+++-- | @write_problem from@ tells if we can read and write to a repo in+-- format @form@. it returns @Nothing@ if there's no problem reading+-- and writing to such a repository.+readfrom_and_writeto_problem :: RepoFormat -> RepoFormat -> Maybe String+readfrom_and_writeto_problem inrf outrf+ | format_has Darcs2 inrf /= format_has Darcs2 outrf+ = Just "Cannot mix darcs-2 repositories with older formats" + | otherwise = msum [read_problem inrf, write_problem outrf]+++-- | @read_problem from@ tells if we can write to a repo in format @form@.+-- it returns @Nothing@ if there's no problem reading from such a repository.+read_problem :: RepoFormat -> Maybe String+read_problem rf | format_has Darcs1_0 rf && format_has Darcs2 rf+ = Just "Invalid repositoryformat: format 2 is incompatible with format 1"+read_problem (RF ks) = unlines `fmap` justsOrNothing (map rp ks)+ where rp x | any is_known x = Nothing+ rp [] = impossible+ rp x = Just $ unwords $+ "Can't understand repository format:" : map BC.unpack x+++-- | Does this version of darcs know how to handle this property?+is_known :: B.ByteString -> Bool+is_known p = p `elem` map rp2ps known_properties++-- | This is the list of properties which this version of darcs knows+-- how to handle.+known_properties :: [RepoProperty]+known_properties = [Darcs1_0, Darcs2, HashedInventory]++justsOrNothing :: [Maybe x] -> Maybe [x]+justsOrNothing mxs =+ case catMaybes mxs of+ [] -> Nothing+ xs -> Just xs++format_has :: RepoProperty -> RepoFormat -> Bool+format_has f (RF ks) = rp2ps f `elem` concat ks++format_has_together :: [RepoProperty] -> RepoFormat -> Bool+format_has_together fs (RF ks) = fht (sort $ map rp2ps fs) ks+ where fht _ [] = False+ fht x (y:ys) | x == sort y = True+ | otherwise = fht x ys++rp2ps :: RepoProperty -> B.ByteString+rp2ps Darcs1_0 = BC.pack "darcs-1.0"+rp2ps Darcs2 = BC.pack "darcs-2"+rp2ps HashedInventory = BC.pack "hashed"
@@ -0,0 +1,417 @@+-- Copyright (C) 2007 David Roundy+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; if not, write to the Free Software Foundation,+-- Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.++{-# OPTIONS_GHC -cpp -fglasgow-exts #-}+{-# LANGUAGE CPP, TypeSynonymInstances, FlexibleInstances #-}++#include "gadts.h"++module Darcs.Repository.HashedIO ( HashedIO, applyHashed,+ copyHashed, syncHashedPristine, copyPartialsHashed, listHashedContents,+ slurpHashedPristine, writeHashedPristine,+ clean_hashdir ) where++import Darcs.Global ( darcsdir )+import Data.List ( (\\) )+import qualified Data.Map as Map+import System.Directory ( getDirectoryContents, createDirectoryIfMissing )+import System.Posix.Types ( EpochTime )+import Control.Monad.State ( StateT, runStateT, modify, get, put, gets, lift )+import Control.Monad ( when )+import Data.Maybe ( isJust )+import System.IO.Unsafe ( unsafeInterleaveIO )++import Darcs.SlurpDirectory.Internal ( Slurpy(..), SlurpyContents(..), map_to_slurpies, slurpies_to_map )+import Darcs.SlurpDirectory ( withSlurpy, undefined_time, undefined_size )+import Darcs.Repository.Cache ( Cache, fetchFileUsingCache, writeFileUsingCache,+ peekInCache, speculateFileUsingCache,+ findFileMtimeUsingCache, setFileMtimeUsingCache,+ okayHash, cleanCachesWithHint, HashedDir(..), hashedDir )+import Darcs.Patch ( Patchy, apply )+import Darcs.RepoPath ( FilePathLike, toFilePath )+import Darcs.IO ( ReadableDirectory(..), WriteableDirectory(..) )+import Darcs.Flags ( DarcsFlag, Compression( .. ), compression )+import Darcs.Lock ( writeAtomicFilePS, removeFileMayNotExist )+import Darcs.Utils ( withCurrentDirectory )+import Progress ( debugMessage, beginTedious, endTedious, tediousSize, finishedOneIO, progress )+import Darcs.Patch.FileName ( FileName, norm_path, fp2fn, fn2fp, fn2niceps, niceps2fn,+ break_on_dir, own_name, super_name )++import ByteStringUtils ( linesPS, unlinesPS )+import qualified Data.ByteString as B (ByteString, length, empty)+import qualified Data.ByteString.Char8 as BC (unpack, pack)++import SHA1 ( sha1PS )++-- | @readHashFile c subdir hash@ reads the file with hash @hash@ in dir subdir,+-- fetching it from 'Cache' @c@ if needed.+readHashFile :: Cache -> HashedDir -> String -> IO (String,B.ByteString)+readHashFile c subdir hash =+ do debugMessage $ "Reading hash file "++hash++" from "++(hashedDir subdir)++"/"+ fetchFileUsingCache c subdir hash++applyHashed :: Patchy q => Cache -> [DarcsFlag] -> String -> q C(x y) -> IO String+applyHashed c fs h p = do s <- slurpHashedPristine c (compression fs) h+ let ms = withSlurpy s $ apply fs p+ case ms of+ Left e -> fail e+ Right (s', ()) -> writeHashedPristine c (compression fs) s'+{-+applyHashed c fs h p = do (_,hd) <- runStateT (apply fs p) $+ HashDir { permissions = RW, cache = c,+ options = fs, rootHash = h }+ return $ rootHash hd+-}++data HashDir r p = HashDir { permissions :: !r, cache :: !Cache,+ compress :: !Compression, rootHash :: !String }+type HashedIO r p = StateT (HashDir r p) IO++data RO = RO+data RW = RW+{-+class Readable r where+ isRO :: r -> Bool+ isRO = const False+instance Readable RW+instance Readable RO where+ isRO RO = True+-}++instance ReadableDirectory (HashedIO r p) where+ mDoesDirectoryExist fn = do thing <- identifyThing fn+ case thing of Just (D,_) -> return True+ _ -> return False+ mDoesFileExist fn = do thing <- identifyThing fn+ case thing of Just (F,_) -> return True+ _ -> return False+ mInCurrentDirectory fn j | fn' == fp2fn "" = j+ | otherwise =+ case break_on_dir fn' of+ Nothing -> do c <- readroot+ case geta D fn' c of+ Nothing -> fail "dir doesn't exist mInCurrentDirectory..."+ Just h -> inh h j+ Just (d,fn'') -> do c <- readroot+ case geta D d c of+ Nothing -> fail "dir doesn't exist..."+ Just h -> inh h $ mInCurrentDirectory fn'' j+ where fn' = norm_path fn+ mGetDirectoryContents = map (\ (_,f,_) -> f) `fmap` readroot+ mReadFilePS fn = mInCurrentDirectory (super_name fn) $ do+ c <- readroot+ case geta F (own_name fn) c of+ Nothing -> fail $ " file don't exist... "++ fn2fp fn+ Just h -> readhash h++instance WriteableDirectory (HashedIO RW p) where+ mWithCurrentDirectory fn j+ | fn' == fp2fn "" = j+ | otherwise =+ case break_on_dir fn' of+ Nothing -> do c <- readroot+ case geta D fn' c of+ Nothing -> fail "dir doesn't exist in mWithCurrentDirectory..."+ Just h -> do (h',x) <- withh h j+ writeroot $ seta D fn' h' c+ return x+ Just (d,fn'') -> do c <- readroot+ case geta D d c of+ Nothing -> fail "dir doesn't exist..."+ Just h -> do (h',x) <- withh h $ mWithCurrentDirectory fn'' j+ writeroot $ seta D d h' c+ return x+ where fn' = norm_path fn+ mSetFileExecutable _ _ = return ()+ mWriteFilePS fn ps = do mexists <- identifyThing fn+ case mexists of+ Just (D,_) -> fail "can't write file over directory"+ _ -> do h <- writeHashFile ps+ makeThing fn (F,h)+ mCreateDirectory fn = do h <- writeHashFile B.empty+ exists <- isJust `fmap` identifyThing fn+ when exists $ fail "can't mCreateDirectory over an existing object."+ makeThing fn (D,h)+ mRename o n = do nexists <- isJust `fmap` identifyThing n+ when nexists $ fail "mRename failed..."+ mx <- identifyThing o+ -- for backwards compatibility accept rename of nonexistent files.+ case mx of Nothing -> return ()+ Just x -> do rmThing o+ makeThing n x+ mRemoveDirectory = rmThing+ mRemoveFile f = do x <- mReadFilePS f+ when (B.length x /= 0) $+ fail $ "Cannot remove non-empty file "++fn2fp f+ rmThing f++identifyThing :: FileName -> HashedIO r p (Maybe (ObjType,String))+identifyThing fn | fn' == fp2fn "" = do h <- gets rootHash+ return $ Just (D, h)+ | otherwise = case break_on_dir fn' of+ Nothing -> getany fn' `fmap` readroot+ Just (d,fn'') -> do c <- readroot+ case geta D d c of+ Nothing -> return Nothing+ Just h -> inh h $ identifyThing fn''+ where fn' = norm_path fn++makeThing :: FileName -> (ObjType,String) -> HashedIO RW p ()+makeThing fn (o,h) = mWithCurrentDirectory (super_name $ norm_path fn) $+ seta o (own_name $ norm_path fn) h `fmap` readroot >>= writeroot++rmThing :: FileName -> HashedIO RW p ()+rmThing fn = mWithCurrentDirectory (super_name $ norm_path fn) $+ do c <- readroot+ let c' = filter (\(_,x,_)->x/= own_name (norm_path fn)) c+ if length c' == length c - 1+ then writeroot c'+ else fail "obj doesn't exist in rmThing"++readhash :: String -> HashedIO r p B.ByteString+readhash h = do c <- gets cache+ z <- lift $ unsafeInterleaveIO $ readHashFile c HashedPristineDir h+ let (_,out) = z+ return out++readTediousHash :: String -> String -> HashedIO r p B.ByteString+readTediousHash k h = do lift $ finishedOneIO k h+ readhash h++gethashmtime :: String -> HashedIO r p EpochTime+gethashmtime h = do HashDir _ c _ _ <- get+ lift $ unsafeInterleaveIO $ findFileMtimeUsingCache c HashedPristineDir h++withh :: String -> HashedIO RW p a -> HashedIO RW p (String,a)+withh h j = do hd <- get+ put $ hd { rootHash = h }+ x <- j+ h' <- gets rootHash+ put hd+ return (h',x)++inh :: String -> HashedIO r p a -> HashedIO r p a+inh h j = do hd <- get+ put $ hd { rootHash = h }+ x <- j+ put hd+ return x++safeInterleave :: HashedIO RO p a -> HashedIO r p a+safeInterleave job = do HashDir _ c compr h <- get+ z <- lift $ unsafeInterleaveIO $ runStateT job+ (HashDir { permissions = RO, cache = c, compress = compr, rootHash = h })+ let (x,_) = z+ return x++readroot :: HashedIO r p [(ObjType, FileName, String)]+readroot = do haveitalready <- peekroot+ cc <- gets rootHash >>= readdir+ when (not haveitalready) $ speculate cc+ return cc+ where speculate :: [(a,b,String)] -> HashedIO r q ()+ speculate c = do cac <- gets cache+ mapM_ (\(_,_,z) -> lift $ speculateFileUsingCache cac HashedPristineDir z) c+ peekroot :: HashedIO r p Bool+ peekroot = do HashDir _ c _ h <- get+ lift $ peekInCache c HashedPristineDir h++writeroot :: [(ObjType, FileName, String)] -> HashedIO r p ()+writeroot c = do h <- writedir c+ modify $ \hd -> hd { rootHash = h }++data ObjType = F | D deriving Eq++-- | @geta objtype name stuff@ tries to get an object of type @objtype@ named @name@+-- in @stuff@.+geta :: ObjType -> FileName -> [(ObjType, FileName, String)] -> Maybe String+geta o f c = do (o',h) <- getany f c+ if o == o' then Just h else Nothing++getany :: FileName -> [(ObjType, FileName, String)] -> Maybe (ObjType,String)+getany _ [] = Nothing+getany f ((o,f',h):_) | f == f' = Just (o,h)+getany f (_:r) = getany f r++seta :: ObjType -> FileName -> String -> [(ObjType, FileName, String)] -> [(ObjType, FileName, String)]+seta o f h [] = [(o,f,h)]+seta o f h ((_,f',_):r) | f == f' = (o,f,h):r+seta o f h (x:xs) = x : seta o f h xs++readdir :: String -> HashedIO r p [(ObjType, FileName, String)]+readdir hash = (parsed . linesPS) `fmap` readhash hash+ where parsed (t:n:h:rest) | t == dir = (D, niceps2fn n, BC.unpack h) : parsed rest+ | t == file = (F, niceps2fn n, BC.unpack h) : parsed rest+ parsed _ = []+dir :: B.ByteString+dir = BC.pack "directory:"+file :: B.ByteString+file = BC.pack "file:"+++writedir :: [(ObjType, FileName, String)] -> HashedIO r p String+writedir c = writeHashFile cps+ where cps = unlinesPS $ concatMap (\ (o,d,h) -> [showO o,fn2niceps d,BC.pack h]) c++[B.empty]+ showO D = dir+ showO F = file++writeHashFile :: B.ByteString -> HashedIO r p String+writeHashFile ps = do c <- gets cache+ compr <- gets compress+ lift $ writeFileUsingCache c compr HashedPristineDir ps++-- |Create a Slurpy representing the pristine content determined by the+-- supplied root hash (which uniquely determines the pristine tree)+slurpHashedPristine :: Cache -> Compression -> String -> IO Slurpy+slurpHashedPristine c compr h = fst `fmap` runStateT slh+ (HashDir { permissions = RO, cache = c,+ compress = compr, rootHash = h })++slh :: HashedIO r p Slurpy+slh = do c <- readroot+ hroot <- gets rootHash+ lift $ beginTedious k+ safeInterleave $ (Slurpy rootdir . SlurpDir (Just hroot) . slurpies_to_map) `fmap` mapM sl c+ where sl (F,n,h) = do ps <- safeInterleave $ readTediousHash k h+ t <- gethashmtime h+ let len = if length h == 75 then read (take 10 h)+ else undefined_size+ return $ Slurpy n $ SlurpFile (Just h, t, len) ps+ sl (D,n,h) = inh h $ do c <- readroot+ lift $ tediousSize k (length c)+ lift $ finishedOneIO k h+ (Slurpy n . SlurpDir (Just h) . slurpies_to_map) `fmap` mapM sl c+ k = "Reading pristine"++rootdir :: FileName+rootdir = fp2fn "."++-- |Write contents of a Slurpy into hashed pristine. Only files that have not+-- not yet been hashed (that is, the hash corresponding to their content is+-- already present in hashed pristine) will be written out, so it is efficient+-- to use this function to update existing pristine cache. Note that the+-- pristine root hash will *not* be updated. You need to do that manually.+writeHashedPristine :: Cache -> Compression -> Slurpy -> IO String+writeHashedPristine c compr sl =+ do beginTedious k+ h <- fst `fmap` runStateT (hsl sl)+ (HashDir { permissions = RW, cache = c,+ compress = compr, rootHash = sha1PS B.empty })+ endTedious k+ return h+ where hsl (Slurpy _ (SlurpDir (Just h) _)) = return h+ hsl (Slurpy _ (SlurpDir Nothing ss)) = do lift $ tediousSize k (Map.size ss)+ mapM hs (map_to_slurpies ss) >>= writedir+ hsl (Slurpy _ (SlurpFile (Just h,_,_) _)) = return h+ hsl (Slurpy _ (SlurpFile _ x)) = writeHashFile x+ hs (Slurpy d (SlurpDir (Just h) _)) = progress k $ return (D, d, h)+ hs s@(Slurpy d (SlurpDir Nothing _)) = do h <- hsl s+ lift $ finishedOneIO k h+ return (D, d, h)+ hs (Slurpy f (SlurpFile (Just h,_,_) _)) = progress k $ return (F, f, h)+ hs s@(Slurpy f (SlurpFile _ _)) = do h <- hsl s+ lift $ finishedOneIO k h+ return (F, f, h)+ k = "Writing pristine"++grab :: FileName -> Slurpy -> Maybe Slurpy+grab _ (Slurpy _ (SlurpFile _ _)) = Nothing+grab fn (Slurpy _ (SlurpDir _ ss)) = fmap (Slurpy fn) $ Map.lookup fn ss++-- | Update timestamps on pristine files to match those in the working directory+-- (which is passed to this function in form of a Slurpy). It needed for the+-- mtime-based unsafeDiff optimisation to work efficiently.+syncHashedPristine :: Cache -> Slurpy -> String -> IO ()+syncHashedPristine c s r = do runStateT sh $ HashDir { permissions=RW, cache=c,+ compress=compression [], rootHash=r }+ return ()+ where sh = do cc <- readroot+ lift $ tediousSize k (length cc)+ mapM_ sh' cc+ sh' (D,n,h) = case progress k $ grab n s of+ Just s' -> lift $ syncHashedPristine c s' h+ Nothing -> return ()+ sh' (F,n,h) = case progress k $ grab n s of+ Just (Slurpy _ (SlurpFile (_,t',l) x)) ->+ do t <- lift $ findFileMtimeUsingCache c HashedPristineDir h+ when (t' /= undefined_time && t' /= t) $+ do ps <- readhash h+ when (B.length ps == fromIntegral l && ps == x) $+ lift $ setFileMtimeUsingCache c HashedPristineDir h t'+ _ -> return ()+ k = "Synchronizing pristine"++copyHashed :: String -> Cache -> Compression -> String -> IO ()+copyHashed k c compr z = do runStateT cph $ HashDir { permissions = RO, cache = c,+ compress = compr, rootHash = z }+ return ()+ where cph = do cc <- readroot+ lift $ tediousSize k (length cc)+ mapM_ cp cc+ cp (F,n,h) = do ps <- readhash h+ lift $ finishedOneIO k (fn2fp n)+ lift $ writeAtomicFilePS (fn2fp n) ps+ cp (D,n,h) = do lift $ createDirectoryIfMissing False (fn2fp n)+ lift $ finishedOneIO k (fn2fp n)+ lift $ withCurrentDirectory (fn2fp n) $ copyHashed k c compr h++copyPartialsHashed :: FilePathLike fp =>+ Cache -> Compression -> String -> [fp] -> IO ()+copyPartialsHashed c compr root = mapM_ (copyPartialHashed c compr root)++copyPartialHashed :: FilePathLike fp => Cache -> Compression -> String -> fp -> IO ()+copyPartialHashed c compr root ff =+ do createDirectoryIfMissing True (basename $ toFilePath ff)+ runStateT (cp $ fp2fn $ toFilePath ff) $+ HashDir { permissions = RO, cache = c,+ compress=compr, rootHash = root }+ return ()+ where basename = reverse . dropWhile ('/' /=) . dropWhile ('/' ==) . reverse+ cp f = do mt <- identifyThing f+ case mt of+ Just (D,h) -> do lift $ createDirectoryIfMissing True (fn2fp f)+ lift $ withCurrentDirectory (fn2fp f) $ copyHashed "" c compr h+ Just (F,h) -> do ps <- readhash h+ lift $ writeAtomicFilePS (fn2fp f) ps+ Nothing -> return ()++-- | Seems to list all hashes reachable from "root".+listHashedContents :: String -> Cache -> String -> IO [String]+listHashedContents k c root =+ do beginTedious k+ tediousSize k 1+ x <- fst `fmap` runStateT (lhc (D,fp2fn ".",root)) (HashDir RO c NoCompression root)+ endTedious k+ return x+ where lhc :: (ObjType, FileName, String) -> HashedIO r a [String]+ lhc (D,dname,d) = do xs <- inh d $ readroot+ lift $ finishedOneIO k (fn2fp dname)+ lift $ tediousSize k (length $ filter (\(x,_,_) -> x == D) xs)+ hcxs <- mapM lhc xs+ return (d:concat hcxs)+ lhc (F,_,h) = return [h]++clean_hashdir :: Cache -> HashedDir -> [String] -> IO ()+clean_hashdir c dir_ hashroots =+ do -- we'll remove obsolete bits of "dir"+ debugMessage $ "Cleaning out " ++ (hashedDir dir_) ++ "..."+ let hashdir = darcsdir ++ "/" ++ (hashedDir dir_) ++ "/"+ hs <- concat `fmap` (mapM (listHashedContents "cleaning up..." c) hashroots)+ fs <- filter okayHash `fmap` getDirectoryContents hashdir+ mapM_ (removeFileMayNotExist . (hashdir++)) (fs \\ hs)+ -- and also clean out any global caches.+ debugMessage "Cleaning out any global caches..."+ cleanCachesWithHint c dir_ (fs \\ hs)
@@ -0,0 +1,401 @@+-- Copyright (C) 2006-2007 David Roundy+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; if not, write to the Free Software Foundation,+-- Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.++{-# OPTIONS_GHC -cpp -fglasgow-exts #-}+{-# LANGUAGE CPP, ScopedTypeVariables #-}++#include "gadts.h"++module Darcs.Repository.HashedRepo ( revert_tentative_changes, finalize_tentative_changes,+ slurp_pristine, sync_repo, clean_pristine,+ copy_pristine, copy_partials_pristine, pristine_from_working,+ apply_to_tentative_pristine,+ replacePristineFromSlurpy,+ add_to_tentative_inventory, remove_from_tentative_inventory,+ read_repo, read_tentative_repo, write_and_read_patch,+ write_tentative_inventory, copy_repo, slurp_all_but_darcs,+ readHashedPristineRoot+ ) where++import System.Directory ( doesFileExist, createDirectoryIfMissing )+import System.IO.Unsafe ( unsafeInterleaveIO )+import System.IO ( stderr, hPutStrLn )+import Data.List ( delete )+import Control.Monad ( unless )++import Workaround ( renameFile )+import Darcs.Flags ( DarcsFlag, Compression )+import Darcs.Patch.Set ( PatchSet, SealedPatchSet )+import Darcs.RepoPath ( FilePathLike, ioAbsoluteOrRemote, toPath )+import Darcs.Repository.Cache ( Cache, fetchFileUsingCache, speculateFileUsingCache,+ writeFileUsingCache,+ unionCaches, repo2cache, okayHash, takeHash,+ HashedDir(..), hashedDir )+import Darcs.Repository.HashedIO ( applyHashed, slurpHashedPristine,+ copyHashed, syncHashedPristine, copyPartialsHashed,+ writeHashedPristine, clean_hashdir )+import Darcs.Repository.InternalTypes ( Repository(..), extractCache )+import Darcs.Hopefully ( PatchInfoAnd, patchInfoAndPatch, n2pia, info,+ extractHash, createHashed )+import Darcs.SlurpDirectory ( Slurpy, empty_slurpy, slurp_remove, slurp )+import Darcs.Patch ( RepoPatch, Patchy, Named, showPatch, patch2patchinfo, readPatch )+import Darcs.Patch.Depends ( commute_to_end, slightly_optimize_patchset )+import Darcs.Patch.Info ( PatchInfo, showPatchInfo, human_friendly, readPatchInfo )+import Darcs.Ordered ( unsafeCoerceP, (:<)(..) )+import Darcs.Patch.FileName ( fp2fn )++import ByteStringUtils ( gzReadFilePS, dropSpace )+import qualified Data.ByteString as B (null, length, readFile, empty+ ,tail, take, drop, ByteString)+import qualified Data.ByteString.Char8 as BC (unpack, dropWhile, break, pack)++import Printer ( Doc, hcat, (<>), ($$), renderString, renderPS, text, invisiblePS )+import SHA1 ( sha1PS )+import Darcs.External ( copyFileOrUrl, cloneFile, fetchFilePS, Cachable( Uncachable ) )+import Darcs.Lock ( writeBinFile, writeDocBinFile, writeAtomicFilePS, appendBinFile, appendDocBinFile )+import Darcs.Utils ( withCurrentDirectory )+import Progress ( beginTedious, tediousSize, endTedious, debugMessage, finishedOneIO )+#include "impossible.h"+import Darcs.Ordered ( FL(..), RL(..),+ mapRL, mapFL, lengthRL )+import Darcs.Sealed ( Sealed(..), seal, unseal )+import Darcs.Global ( darcsdir )++revert_tentative_changes :: IO ()+revert_tentative_changes =+ do cloneFile (darcsdir++"/hashed_inventory") (darcsdir++"/tentative_hashed_inventory")+ i <- gzReadFilePS (darcsdir++"/hashed_inventory")+ writeBinFile (darcsdir++"/tentative_pristine") $ "pristine:" ++ inv2pris i++finalize_tentative_changes :: RepoPatch p => Repository p C(r u t) -> Compression -> IO ()+finalize_tentative_changes r compr =+ do let t = darcsdir++"/tentative_hashed_inventory"+ -- first let's optimize it...+ debugMessage "Optimizing the inventory..."+ ps <- read_tentative_repo r "."+ write_tentative_inventory (extractCache r) compr ps+ -- then we'll add in the pristine cache,+ i <- gzReadFilePS t+ p <- gzReadFilePS $ darcsdir++"/tentative_pristine"+ writeDocBinFile t $ pris2inv (inv2pris p) i+ -- and rename it to its final value+ renameFile t $ darcsdir++"/hashed_inventory"+ -- note: in general we can't clean the pristine cache, because a+ -- simultaneous get might be in progress++readHashedPristineRoot :: Repository p C(r u t) -> IO (Maybe String)+readHashedPristineRoot (Repo d _ _ _) =+ withCurrentDirectory d $ do+ i <- (Just `fmap` gzReadFilePS (darcsdir++"/hashed_inventory")) `catch` (\_ -> return Nothing)+ return $ inv2pris `fmap` i++clean_pristine :: Repository p C(r u t) -> IO ()+clean_pristine r@(Repo d _ _ _) = withCurrentDirectory d $+ do -- we'll remove obsolete bits of our pristine cache+ debugMessage "Cleaning out the pristine cache..."+ i <- gzReadFilePS (darcsdir++"/hashed_inventory")+ clean_hashdir (extractCache r) HashedPristineDir [inv2pris i]++add_to_tentative_inventory :: RepoPatch p => Cache -> Compression -> PatchInfoAnd p C(x y) -> IO FilePath+add_to_tentative_inventory c compr p =+ do hash <- snd `fmap` write_patch_if_necesary c compr p+ appendDocBinFile (darcsdir++"/tentative_hashed_inventory") $ showPatchInfo $ info p+ appendBinFile (darcsdir++"/tentative_hashed_inventory") $ "\nhash: " ++ hash ++ "\n"+ return $ darcsdir++"/patches/" ++ hash++remove_from_tentative_inventory :: RepoPatch p => Repository p C(r u t) -> Compression+ -> FL (Named p) C(x t) -> IO ()+remove_from_tentative_inventory repo compr to_remove =+ -- FIXME: This algorithm should be *far* simpler. All we need do is+ -- to to remove the patches from a patchset and then write that+ -- patchset. The commutation behavior of PatchInfoAnd should track+ -- which patches need to be rewritten for us.+ do allpatches <- read_tentative_repo repo "."+ skipped :< _ <- return $ commute_to_end to_remove allpatches+ okay <- simple_remove_from_tentative_inventory repo compr+ (mapFL patch2patchinfo to_remove ++ mapFL patch2patchinfo skipped)+ unless okay $ bug "bug in HashedRepo.remove_from_tentative_inventory"+ sequence_ $ mapFL (add_to_tentative_inventory (extractCache repo) compr . n2pia) skipped++simple_remove_from_tentative_inventory :: forall p C(r u t). RepoPatch p =>+ Repository p C(r u t) -> Compression -> [PatchInfo] -> IO Bool+simple_remove_from_tentative_inventory repo compr pis = do+ inv <- read_tentative_repo repo "."+ case cut_inv pis inv of+ Nothing -> return False+ Just (Sealed inv') -> do write_tentative_inventory (extractCache repo) compr inv'+ return True+ where cut_inv :: [PatchInfo] -> PatchSet p C(x) -> Maybe (SealedPatchSet p)+ cut_inv [] x = Just $ seal x+ cut_inv x (NilRL:<:rs) = cut_inv x rs+ cut_inv xs ((hp:<:r):<:rs) | info hp `elem` xs = cut_inv (info hp `delete` xs) (r:<:rs)+ cut_inv _ _ = Nothing++writeHashFile :: Cache -> Compression -> HashedDir -> Doc -> IO String+writeHashFile c compr subdir d = do debugMessage $ "Writing hash file to "++(hashedDir subdir)+ writeFileUsingCache c compr subdir $ renderPS d++read_repo :: RepoPatch p => Repository p C(r u t) -> String -> IO (PatchSet p C(r))+read_repo repo d = do+ realdir <- toPath `fmap` ioAbsoluteOrRemote d+ Sealed ps <- read_repo_private repo realdir "hashed_inventory" `catch`+ (\e -> do hPutStrLn stderr ("Invalid repository: " ++ realdir)+ ioError e)+ return $ unsafeCoerceP ps++read_tentative_repo :: RepoPatch p => Repository p C(r u t) -> String -> IO (PatchSet p C(t))+read_tentative_repo repo d = do+ realdir <- toPath `fmap` ioAbsoluteOrRemote d+ Sealed ps <- read_repo_private repo realdir "tentative_hashed_inventory" `catch`+ (\e -> do hPutStrLn stderr ("Invalid repository: " ++ realdir)+ ioError e)+ return $ unsafeCoerceP ps++read_repo_private :: RepoPatch p => Repository p C(r u t)+ -> FilePath -> FilePath -> IO (SealedPatchSet p)+read_repo_private repo d iname =+ do inventories <- read_inventory_private repo (d++"/"++darcsdir) iname+ parseinvs inventories+ where read_patches :: RepoPatch p => [(PatchInfo, String)] -> IO (Sealed (RL (PatchInfoAnd p) C(x)))+ read_patches [] = return $ seal NilRL+ read_patches allis@((i1,h1):is1) =+ lift2Sealed (\p rest -> i1 `patchInfoAndPatch` p :<: rest)+ (rp is1)+ (createHashed h1 (const $ speculate h1 allis >> parse i1 h1))+ where rp :: RepoPatch p => [(PatchInfo, String)] -> IO (Sealed (RL (PatchInfoAnd p) C(x)))+ rp [] = return $ seal NilRL+ rp [(i,h),(il,hl)] =+ lift2Sealed (\p rest -> i `patchInfoAndPatch` p :<: rest)+ (rp [(il,hl)])+ (createHashed h (const $ speculate h (reverse allis) >> parse i h))+ rp ((i,h):is) = lift2Sealed (\p rest -> i `patchInfoAndPatch` p :<: rest)+ (rp is)+ (createHashed h (parse i))+ speculate :: String -> [(PatchInfo, String)] -> IO ()+ speculate h is = do already_got_one <- doesFileExist (d++"/"++darcsdir++"/patches/"++h)+ unless already_got_one $+ mapM_ (speculateFileUsingCache (extractCache repo) HashedPatchesDir . snd) is+ parse :: Patchy p => PatchInfo -> String -> IO (Sealed (p C(x)))+ parse i h = do debugMessage ("Reading patch file: "++ show (human_friendly i))+ (fn,ps) <- fetchFileUsingCache (extractCache repo) HashedPatchesDir h+ case readPatch ps of+ Just (p,_) -> return p+ Nothing -> fail $ unlines ["Couldn't parse file "++fn,+ "which is patch",+ renderString $ human_friendly i]+ parseinvs :: RepoPatch p => [[(PatchInfo, String)]] -> IO (SealedPatchSet p)+ parseinvs [] = return $ seal NilRL+ parseinvs (i:is) = lift2Sealed (:<:) (parseinvs is) (read_patches i)+ lift2Sealed :: (FORALL(y z) q C(y z) -> p C(x y) -> r C(x z))+ -> IO (Sealed (p C(x))) -> (FORALL(b) IO (Sealed (q C(b)))) -> IO (Sealed (r C(x)))+ lift2Sealed f iox ioy = do Sealed x <- unseal seal `fmap` unsafeInterleaveIO iox+ Sealed y <- unseal seal `fmap` unsafeInterleaveIO ioy+ return $ seal $ f y x++write_and_read_patch :: RepoPatch p => Cache -> Compression -> PatchInfoAnd p C(x y)+ -> IO (PatchInfoAnd p C(x y))+write_and_read_patch c compr p = do (i,h) <- write_patch_if_necesary c compr p+ unsafeInterleaveIO $ readp h i+ where parse i h = do debugMessage ("Rereading patch file: "++ show (human_friendly i))+ (fn,ps) <- fetchFileUsingCache c HashedPatchesDir h+ case readPatch ps of+ Just (x,_) -> return x+ Nothing -> fail $ unlines ["Couldn't parse patch file "++fn,+ "which is",+ renderString $ human_friendly i]+ readp h i = do Sealed x <- createHashed h (parse i)+ return $ patchInfoAndPatch i $ unsafeCoerceP x++write_tentative_inventory :: RepoPatch p => Cache -> Compression -> PatchSet p C(x) -> IO ()+write_tentative_inventory c compr = write_either_inventory c compr "tentative_hashed_inventory"++copy_repo :: RepoPatch p => Repository p C(r u t) -> [DarcsFlag] -> String -> IO ()+copy_repo repo@(Repo outr _ _ _) opts inr = do+ createDirectoryIfMissing False (outr++"/"++darcsdir++"/inventories")+ copyFileOrUrl opts (inr++"/"++darcsdir++"/hashed_inventory") (outr++"/"++darcsdir++"/hashed_inventory")+ Uncachable -- no need to copy anything but hashed_inventory!+ appendBinFile (outr++"/"++darcsdir++"/prefs/sources") (show $ repo2cache inr `unionCaches` extractCache repo)+ debugMessage "Done copying hashed inventory."++write_either_inventory :: RepoPatch p => Cache -> Compression -> String -> PatchSet p C(x) -> IO ()+write_either_inventory c compr iname x =+ do createDirectoryIfMissing False $ darcsdir++"/inventories"+ let k = "Writing inventory"+ beginTedious k+ tediousSize k (lengthRL x)+ hsh <- write_inventory_private k c compr $ slightly_optimize_patchset x+ endTedious k+ case hsh of+ Nothing -> writeBinFile (darcsdir++"/"++iname) ""+ Just h -> gzReadFilePS (darcsdir++"/inventories/"++h) >>= writeAtomicFilePS (darcsdir++"/"++iname)++write_inventory_private :: RepoPatch p => String -> Cache -> Compression+ -> PatchSet p C(x) -> IO (Maybe String)+write_inventory_private _ _ _ NilRL = return Nothing+write_inventory_private _ _ _ (NilRL:<:NilRL) = return Nothing+write_inventory_private _ _ _ (NilRL:<:_) = -- This shouldn't be possible, so best to check...+ bug "malformed PatchSet in HashedRepo.write_inventory_private"+write_inventory_private k c compr (x:<:xs) =+ do resthash <- write_inventory_private k c compr xs+ finishedOneIO k (case resthash of Nothing -> ""; Just h -> h)+ inventory <- sequence $ mapRL (write_patch_if_necesary c compr) x+ let inventorylist = hcat (map pihash $ reverse inventory)+ inventorycontents = case resthash of+ Just lasthash -> text ("Starting with inventory:\n"++lasthash) $$+ inventorylist+ _ -> inventorylist+ hash <- writeHashFile c compr HashedInventoriesDir inventorycontents+ return $ Just hash++write_patch_if_necesary :: RepoPatch p => Cache -> Compression+ -> PatchInfoAnd p C(x y) -> IO (PatchInfo, String)+write_patch_if_necesary c compr hp =+ seq infohp $ case extractHash hp of+ Right h -> return (infohp, h)+ Left p -> (\h -> (infohp, h)) `fmap`+ writeHashFile c compr HashedPatchesDir (showPatch p)+ where infohp = info hp++pihash :: (PatchInfo,String) -> Doc+pihash (pinf,hash) = showPatchInfo pinf $$ text ("hash: " ++ hash ++ "\n")++read_inventory_private :: Repository p C(r u t) -> String -> String+ -> IO [[(PatchInfo, String)]]+read_inventory_private repo d iname = do+ i <- skip_pristine `fmap` fetchFilePS (d++"/"++iname) Uncachable+ (rest,str) <- case BC.break ((==)'\n') i of+ (swt,pistr) | swt == BC.pack "Starting with inventory:" ->+ case BC.break ((==)'\n') $ B.tail pistr of+ (h,thisinv) | okayHash $ BC.unpack h ->+ do r <- unsafeInterleaveIO $ read_inventories+ (extractCache repo) (BC.unpack h) -- don't unpack twice!+ return (r,thisinv)+ _ -> fail $ "Bad hash in " ++ d ++ "/"++darcsdir++"/" ++ iname+ _ -> return ([],i)+ return $ reverse (read_patch_ids str) : rest++read_inventories :: Cache -> String -> IO [[(PatchInfo, String)]]+read_inventories cache ihash = do+ (fn,i_and_p) <- fetchFileUsingCache cache HashedInventoriesDir ihash+ let i = skip_pristine i_and_p+ (rest,str) <- case BC.break ((==)'\n') i of+ (swt,pistr) | swt == BC.pack "Starting with inventory:" ->+ case BC.break ((==)'\n') $ B.tail pistr of+ (h,thisinv) | okayHash $ BC.unpack h ->+ do r <- unsafeInterleaveIO $+ read_inventories cache (BC.unpack h) -- again. no.+ return (r,thisinv)+ _ -> fail $ "Bad hash in file " ++ fn+ _ -> return ([],i)+ return $ reverse (read_patch_ids str) : rest++read_patch_ids :: B.ByteString -> [(PatchInfo, String)]+read_patch_ids inv | B.null inv = []+read_patch_ids inv = case readPatchInfo inv of+ Nothing -> []+ Just (pinfo,r) ->+ case readHash r of+ Nothing -> []+ Just (h,r') -> (pinfo,h) : read_patch_ids r'++readHash :: B.ByteString -> Maybe (String, B.ByteString)+readHash s = let s' = dropSpace s+ (l,r) = BC.break ((==)'\n') s'+ (kw,h) = BC.break ((==)' ') l+ in if kw /= BC.pack "hash:" || B.length h <= 1+ then Nothing+ else Just (BC.unpack $ B.tail h,r)++apply_pristine :: Patchy q => Cache -> [DarcsFlag] -> String -> String -> q C(x y) -> IO ()+apply_pristine c opts d iname p =+ do i <- gzReadFilePS (d++"/"++iname)+ h <- applyHashed c opts (inv2pris i) p+ writeDocBinFile (d++"/"++iname) $ pris2inv h i++apply_to_tentative_pristine :: Patchy q => Cache -> [DarcsFlag] -> q C(x y) -> IO ()+apply_to_tentative_pristine c opts p = apply_pristine c opts "." (darcsdir++"/tentative_pristine") p++slurp_pristine :: Cache -> Compression -> String -> String -> IO Slurpy+slurp_pristine c compr d iname = do+ i <- fetchFilePS (d++"/"++iname) Uncachable+ slurp_pristine_private c compr i++slurp_pristine_private :: Cache -> Compression -> B.ByteString -> IO Slurpy+slurp_pristine_private c compr inv = case inv2pris inv of+ h | h == sha1PS B.empty -> return empty_slurpy+ | otherwise -> slurpHashedPristine c compr h++pristine_from_working :: Cache -> Compression -> IO ()+pristine_from_working c compr = do+ s <- slurp_all_but_darcs "."+ replacePristineFromSlurpy c compr s++replacePristineFromSlurpy :: Cache -> Compression -> Slurpy -> IO ()+replacePristineFromSlurpy c compr s = do + h <- writeHashedPristine c compr s+ let t = darcsdir++"/hashed_inventory"+ i <- gzReadFilePS t+ writeDocBinFile t $ pris2inv h i++copy_pristine :: Cache -> Compression -> String -> String -> IO ()+copy_pristine c compr d iname = do+ i <- fetchFilePS (d++"/"++iname) Uncachable+ debugMessage $ "Copying hashed pristine tree: "++inv2pris i+ let k = "Copying pristine"+ beginTedious k+ copyHashed k c compr $ inv2pris i+ endTedious k++sync_repo :: Cache -> IO ()+sync_repo c = do i <- B.readFile $ darcsdir++"/hashed_inventory"+ s <- slurp_all_but_darcs "."+ beginTedious "Synchronizing pristine"+ syncHashedPristine c s $ inv2pris i+ ++copy_partials_pristine :: FilePathLike fp =>+ Cache -> Compression -> String -> String -> [fp] -> IO ()+copy_partials_pristine c compr d iname fps =+ do i <- fetchFilePS (d++"/"++iname) Uncachable+ copyPartialsHashed c compr (inv2pris i) fps++inv2pris :: B.ByteString -> String+inv2pris inv | B.take pristine_name_length inv == pristine_name =+ case takeHash $ B.drop pristine_name_length inv of+ Just (h,_) -> h+ Nothing -> error "Bad hash in inventory!"+ | otherwise = sha1PS B.empty++pris2inv :: String -> B.ByteString -> Doc+pris2inv h inv = invisiblePS pristine_name <> text h $$ invisiblePS (skip_pristine inv)++pristine_name :: B.ByteString+pristine_name = BC.pack "pristine:"++skip_pristine :: B.ByteString -> B.ByteString+skip_pristine ps+ | B.take pristine_name_length ps == pristine_name = B.drop 1 $ BC.dropWhile (/= '\n') $+ B.drop pristine_name_length ps+ | otherwise = ps++pristine_name_length :: Int+pristine_name_length = B.length pristine_name++slurp_all_but_darcs :: FilePath -> IO Slurpy+slurp_all_but_darcs d = do s <- slurp d+ case slurp_remove (fp2fn $ "./"++darcsdir) s of+ Nothing -> return s+ Just s' -> return s'
@@ -0,0 +1,1062 @@+-- Copyright (C) 2002-2004,2007-2008 David Roundy+-- Copyright (C) 2005 Juliusz Chroboczek+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.++{-# OPTIONS_GHC -cpp -fglasgow-exts #-}+{-# LANGUAGE CPP, ScopedTypeVariables, Rank2Types, RankNTypes, PatternGuards #-}++#include "gadts.h"++module Darcs.Repository.Internal ( Repository(..), RepoType(..), RIO(unsafeUnRIO), ($-),+ maybeIdentifyRepository, identifyDarcs1Repository, identifyRepositoryFor,+ findRepository, amInRepository, amNotInRepository,+ slurp_pending, pristineFromWorking, revertRepositoryChanges,+ slurp_recorded, slurp_recorded_and_unrecorded,+ read_pending, announce_merge_conflicts, setTentativePending,+ check_unrecorded_conflicts,+ withRecorded,+ checkPristineAgainstSlurpy,+ get_unrecorded, get_unrecorded_unsorted, get_unrecorded_no_look_for_adds,+ get_unrecorded_in_files, get_unrecorded_in_files_unsorted,+ read_repo, sync_repo,+ prefsUrl, makePatchLazy,+ add_to_pending,+ withRepoLock, withRepoReadLock,+ withRepository, withRepositoryDirectory, withGutsOf,+ tentativelyAddPatch, tentativelyRemovePatches, tentativelyAddToPending,+ tentativelyReplacePatches,+ tentativelyMergePatches, considerMergeToWorking,+ finalizeRepositoryChanges,+ unrevertUrl,+ applyToWorking, patchSetToPatches,+ createPristineDirectoryTree, createPartialsPristineDirectoryTree,+ replacePristineFromSlurpy,+ optimizeInventory, cleanRepository,+ getMarkedupFile,+ PatchSet, SealedPatchSet,+ setScriptsExecutable,+ getRepository, rIO,+ testTentative, testRecorded+ ) where++import Printer ( putDocLn, (<+>), text, ($$) )++import Data.Maybe ( isJust, isNothing )+import Darcs.Repository.Prefs ( get_prefval )+import Darcs.Resolution ( standard_resolution, external_resolution )+import System.Exit ( ExitCode(..), exitWith )+import System.Cmd ( system )+import Darcs.External ( backupByCopying, clonePartialsTree )+import Darcs.IO ( runTolerantly, runSilently )+import Darcs.Repository.Pristine ( identifyPristine, nopristine,+ easyCreatePristineDirectoryTree, slurpPristine, syncPristine,+ easyCreatePartialsPristineDirectoryTree,+ createPristineFromWorking )+import qualified Darcs.Repository.Pristine as Pristine ( replacePristineFromSlurpy )+ +import Data.List ( (\\) )+import Darcs.SignalHandler ( withSignalsBlocked )+import Darcs.Repository.Format ( RepoFormat, RepoProperty( Darcs2, HashedInventory ),+ identifyRepoFormat, format_has,+ write_problem, read_problem, readfrom_and_writeto_problem )+import System.Directory ( doesDirectoryExist, setCurrentDirectory, removeFile,+ createDirectoryIfMissing )+import Control.Monad ( liftM, when, unless )+import Workaround ( getCurrentDirectory, renameFile, setExecutable )++import ByteStringUtils ( gzReadFilePS )+import qualified Data.ByteString as B (ByteString, empty, readFile, isPrefixOf)+import qualified Data.ByteString.Char8 as BC (pack)++import Darcs.Patch ( Patch, RealPatch, Effect, is_hunk, is_binary, description,++ try_to_shrink, commuteFL, commute, apply_to_filepaths )+import Darcs.Patch.Prim ( try_shrinking_inverse, Conflict )+import Darcs.Patch.Bundle ( scan_bundle, make_bundle )+import Darcs.Patch.FileName ( FileName, fn2fp )+import Darcs.Patch.TouchesFiles ( choose_touching )+import Darcs.SlurpDirectory ( Slurpy, slurp_unboring, mmap_slurp, co_slurp,+ slurp_has, list_slurpy_files )+import Darcs.Hopefully ( PatchInfoAnd, info, n2pia,+ hopefully, hopefullyM )+import Darcs.Repository.ApplyPatches ( apply_patches )+import qualified Darcs.Repository.HashedRepo as HashedRepo+ ( revert_tentative_changes, finalize_tentative_changes,+ remove_from_tentative_inventory, sync_repo,+ copy_pristine, copy_partials_pristine, slurp_pristine,+ apply_to_tentative_pristine, pristine_from_working,+ write_tentative_inventory, write_and_read_patch,+ add_to_tentative_inventory,+ read_repo, read_tentative_repo, clean_pristine,+ replacePristineFromSlurpy,+ slurp_all_but_darcs )+import qualified Darcs.Repository.DarcsRepo as DarcsRepo+import Darcs.Flags ( DarcsFlag(AnyOrder, Boring, LookForAdds, Verbose, Quiet,+ MarkConflicts, AllowConflicts, NoUpdateWorking,+ WorkRepoUrl, WorkRepoDir, UMask, Test, LeaveTestDir,+ SetScriptsExecutable, DryRun, IgnoreTimes,+ Summary, NoSummary),+ want_external_merge, compression )+import Darcs.Ordered ( FL(..), RL(..), EqCheck(..), unsafeCoerceP,+ (:\/:)(..), (:/\:)(..), (:>)(..),+ (+>+), lengthFL, nullFL,+ allFL, filterFL,+ reverseRL, reverseFL, concatRL, mapFL,+ mapFL_FL, concatFL )+import Darcs.Patch ( RepoPatch, Patchy, Prim, merge,+ joinPatches, sort_coalesceFL,+ list_conflicted_files, list_touched_files,+ Named, patchcontents, anonymous,+ commuteRL, fromPrims,+ patch2patchinfo, readPatch,+ writePatch, effect, invert,+ is_addfile, is_adddir,+ is_setpref,+ apply, apply_to_slurpy,+ empty_markedup_file, MarkedUpFile+ )+import Darcs.Patch.Patchy ( Invert(..) )+import Darcs.Patch.Permutations ( commuteWhatWeCanFL, removeFL )+import Darcs.Patch.Info ( PatchInfo )+import Darcs.Patch.Set ( PatchSet, SealedPatchSet )+import Darcs.Patch.Apply ( markup_file, LineMark(None) )+import Darcs.Patch.Depends ( get_common_and_uncommon, deep_optimize_patchset )+import Darcs.Diff ( unsafeDiffAtPaths, unsafeDiff )+import Darcs.RepoPath ( FilePathLike, AbsolutePath, toFilePath,+ ioAbsoluteOrRemote, toPath )+import Darcs.Utils ( promptYorn, catchall, withCurrentDirectory, withUMask, nubsort )+import Progress ( debugMessage )+import Darcs.ProgressPatches (progressFL)+import Darcs.URL ( is_file )+import Darcs.Repository.Prefs ( darcsdir_filter, boring_file_filter, filetype_function,+ getCaches )+import Darcs.Lock ( withLock, writeDocBinFile, withDelayedDir, removeFileMayNotExist,+ withTempDir, withPermDir )+import Darcs.Sealed ( Sealed(Sealed), seal, FlippedSeal(FlippedSeal), flipSeal )+import Darcs.Repository.InternalTypes( Repository(..), RepoType(..) )+import Darcs.Global ( darcsdir )+#include "impossible.h"++-- | Repository IO monad. This monad-like datatype is responsible for+-- sequencing IO actions that modify the tentative recorded state of+-- the repository.+newtype RIO p C(r u t t1) a = RIO {+ unsafeUnRIO :: Repository p C(r u t) -> IO a -- ^ converts @RIO a@ to @IO a@.+ }++-- | This is just like @>>=@ from the Monad class except that it+-- respects type witness safe repository transformations. Even so, it+-- only tracks modifications to the tentative recorded state.+(>>>=) :: RIO p C(r u t t1) a -> (a -> RIO p C(r u t1 t2) b) -> RIO p C(r u t t2) b+m >>>= k = RIO $ \ (Repo x y z w) ->+ do a <- unsafeUnRIO m (Repo x y z w)+ unsafeUnRIO (k a) (Repo x y z w)++-- | This corresponds to @>>@ from the Monad class.+(>>>) :: RIO p C(r u t t1) a -> RIO p C(r u t1 t2) b -> RIO p C(r u t t2) b+a >>> b = a >>>= (const b)++-- | This corresponds to @return@ from the Monad class.+returnR :: a -> RIO p C(r u t t) a+returnR = rIO . return++-- | This the @RIO@ equivalent of @liftIO@.+rIO :: IO a -> RIO p C(r u t t) a+rIO = RIO . const++instance Functor (RIO p C(r u t t)) where+ fmap f m = RIO $ \r -> fmap f (unsafeUnRIO m r)++-- | We have an instance of Monad so that IO actions that do not+-- change the tentative recorded state are convenient in the IO monad.+instance Monad (RIO p C(r u t t)) where+ (>>=) = (>>>=)+ (>>) = (>>>)+ return = returnR+ fail = rIO . fail++-- | Similar to the @ask@ function of the MonadReader class.+-- This allows actions in the RIO monad to get the current+-- repository.+-- FIXME: Don't export this. If we don't export this+-- it makes it harder for arbitrary IO actions to access+-- the repository and hence our code is easier to audit.+getRepository :: RIO p C(r u t t) (Repository p C(r u t))+getRepository = RIO return++maybeIdentifyRepository :: [DarcsFlag] -> String -> IO (Either String (Repository p C(r u t)))+maybeIdentifyRepository opts "." =+ do darcs <- doesDirectoryExist darcsdir+ rf_or_e <- identifyRepoFormat "."+ here <- toPath `fmap` ioAbsoluteOrRemote "."+ case rf_or_e of+ Left err -> return $ Left err+ Right rf ->+ case read_problem rf of+ Just err -> return $ Left err+ Nothing -> if darcs then do pris <- identifyPristine+ cs <- getCaches opts here+ return $ Right $ Repo here opts rf (DarcsRepository pris cs)+ else return (Left "Not a repository")+maybeIdentifyRepository opts url' =+ do url <- toPath `fmap` ioAbsoluteOrRemote url'+ rf_or_e <- identifyRepoFormat url+ case rf_or_e of+ Left e -> return $ Left e+ Right rf -> case read_problem rf of+ Just err -> return $ Left err+ Nothing -> do cs <- getCaches opts url+ return $ Right $ Repo url opts rf (DarcsRepository nopristine cs)++identifyDarcs1Repository :: [DarcsFlag] -> String -> IO (Repository Patch C(r u t))+identifyDarcs1Repository opts url =+ do er <- maybeIdentifyRepository opts url+ case er of+ Left s -> fail s+ Right r -> return r++identifyRepositoryFor :: forall p C(r u t). RepoPatch p => Repository p C(r u t) -> String -> IO (Repository p C(r u t))+identifyRepositoryFor (Repo _ opts rf _) url =+ do Repo absurl _ rf_ t <- identifyDarcs1Repository opts url+ let t' = case t of DarcsRepository x c -> DarcsRepository x c+ case readfrom_and_writeto_problem rf_ rf of+ Just e -> fail $ "Incompatibility with repository " ++ url ++ ":\n" ++ e+ Nothing -> return $ Repo absurl opts rf_ t'++isRight :: Either a b -> Bool+isRight (Right _) = True+isRight _ = False++currentDirIsRepository :: IO Bool+currentDirIsRepository = isRight `liftM` maybeIdentifyRepository [] "."++amInRepository :: [DarcsFlag] -> IO (Either String ())+amInRepository (WorkRepoDir d:_) =+ do setCurrentDirectory d `catchall` (fail $ "can't set directory to "++d)+ air <- currentDirIsRepository+ if air+ then return (Right ())+ else return (Left "You need to be in a repository directory to run this command.")+amInRepository (_:fs) = amInRepository fs+amInRepository [] =+ seekRepo (Left "You need to be in a repository directory to run this command.")++-- | hunt upwards for the darcs repository+-- This keeps changing up one parent directory, testing at each+-- step if the current directory is a repository or not. $+-- WARNING this changes the current directory for good if matchFn succeeds+seekRepo :: Either String ()+ -- ^ what to return if we don't find a repository+ -> IO (Either String ())+seekRepo onFail = getCurrentDirectory >>= helper where+ helper startpwd = do+ air <- currentDirIsRepository+ if air+ then return (Right ())+ else do cd <- toFilePath `fmap` getCurrentDirectory+ setCurrentDirectory ".."+ cd' <- toFilePath `fmap` getCurrentDirectory+ if cd' /= cd+ then helper startpwd+ else do setCurrentDirectory startpwd+ return onFail++amNotInRepository :: [DarcsFlag] -> IO (Either String ())+amNotInRepository (WorkRepoDir d:_) = do createDirectoryIfMissing False d+ -- note that the above could always fail+ setCurrentDirectory d+ amNotInRepository []+amNotInRepository (_:f) = amNotInRepository f+amNotInRepository [] =+ do air <- currentDirIsRepository+ if air then return (Left $ "You may not run this command in a repository.")+ else return $ Right ()++findRepository :: [DarcsFlag] -> IO (Either String ())+findRepository (WorkRepoUrl d:_) | is_file d =+ do setCurrentDirectory d `catchall` (fail $ "can't set directory to "++d)+ findRepository []+findRepository (WorkRepoDir d:_) =+ do setCurrentDirectory d `catchall` (fail $ "can't set directory to "++d)+ findRepository []+findRepository (_:fs) = findRepository fs+findRepository [] = seekRepo (Right ())++slurp_pending :: RepoPatch p => Repository p C(r u t) -> IO Slurpy+slurp_pending repo@(Repo _ _ _ rt) = do+ cur <- slurp_recorded repo+ Sealed pend <- read_pending repo+ case apply_to_slurpy pend cur of+ Just pendcur -> return pendcur+ Nothing -> do putStrLn "Yikes, pending has conflicts. Renaming file as_darcs/patches/pending_buggy"+ renameFile (pendingName rt) (pendingName rt++"_buggy")+ return cur++slurp_recorded :: RepoPatch p => Repository p C(r u t) -> IO Slurpy+slurp_recorded (Repo dir opts rf (DarcsRepository _ c))+ | format_has HashedInventory rf =+ HashedRepo.slurp_pristine c (compression opts) dir $ darcsdir++"/hashed_inventory"+slurp_recorded repository@(Repo dir _ _ (DarcsRepository p _)) = do+ mc <- withCurrentDirectory dir $ slurpPristine p+ case mc of (Just slurpy) -> return slurpy+ Nothing -> withDelayedDir "pristine.temp" $ \abscd ->+ do let cd = toFilePath abscd+ createPristineDirectoryTree repository cd+ mmap_slurp cd++slurp_recorded_and_unrecorded :: RepoPatch p => Repository p C(r u t) -> IO (Slurpy, Slurpy)+slurp_recorded_and_unrecorded repo@(Repo r _ _ _) = do+ cur <- slurp_recorded repo+ Sealed pend <- read_pending repo+ withCurrentDirectory r $+ case apply_to_slurpy pend cur of+ Nothing -> fail "Yikes, pending has conflicts!"+ Just pendslurp -> do unrec <- co_slurp pendslurp "."+ return (cur, unrec)++pendingName :: RepoType p -> String+pendingName (DarcsRepository _ _) = darcsdir++"/patches/pending"++read_pending :: RepoPatch p => Repository p C(r u t) -> IO (Sealed (FL Prim C(r)))+read_pending (Repo r _ _ tp) =+ withCurrentDirectory r (read_pendingfile (pendingName tp))++add_to_pending :: RepoPatch p => Repository p C(r u t) -> FL Prim C(u y) -> IO ()+add_to_pending (Repo _ opts _ _) _ | NoUpdateWorking `elem` opts = return ()+add_to_pending repo p =+ do pend <- get_unrecorded repo+ make_new_pending repo (pend +>+ p)++readPrims :: B.ByteString -> Sealed (FL Prim C(x))+readPrims s = case readPatch s :: Maybe (Sealed (Patch C(x )), B.ByteString) of+ Nothing -> Sealed NilFL+ Just (Sealed p,_) -> Sealed (effect p)++read_pendingfile :: String -> IO (Sealed (FL Prim C(x)))+read_pendingfile name = do+ pend <- gzReadFilePS name `catchall` return B.empty+ return $ readPrims pend++make_new_pending :: forall p C(r u t y). RepoPatch p => Repository p C(r u t) -> FL Prim C(r y) -> IO ()+make_new_pending (Repo _ opts _ _) _ | NoUpdateWorking `elem` opts = return ()+make_new_pending repo@(Repo r _ _ tp) origp =+ withCurrentDirectory r $+ do let newname = pendingName tp ++ ".new"+ debugMessage $ "Writing new pending: " ++ newname+ Sealed sfp <- return $ sift_for_pending origp+ writeSealedPatch newname $ seal $ fromPrims $ sfp+ cur <- slurp_recorded repo+ Sealed p <- read_pendingfile newname+ when (isNothing $ apply_to_slurpy p cur) $ do+ let buggyname = pendingName tp ++ "_buggy"+ renameFile newname buggyname+ bugDoc $ text "There was an attempt to write an invalid pending!"+ $$ text "If possible, please send the contents of"+ <+> text buggyname+ $$ text "along with a bug report."+ renameFile newname (pendingName tp)+ debugMessage $ "Finished writing new pending: " ++ newname+ where writeSealedPatch :: FilePath -> Sealed (Patch C(x)) -> IO ()+ writeSealedPatch fp (Sealed p) = writePatch fp p++sift_for_pending :: FL Prim C(x y) -> Sealed (FL Prim C(x))+sift_for_pending simple_ps =+ let oldps = maybe simple_ps id $ try_shrinking_inverse $ crude_sift simple_ps+ in if allFL (\p -> is_addfile p || is_adddir p) $ oldps+ then seal oldps+ else fromJust $ do+ Sealed x <- return $ sfp NilFL $ reverseFL oldps+ return (case try_to_shrink x of+ ps | lengthFL ps < lengthFL oldps -> sift_for_pending ps+ | otherwise -> seal ps)+ where sfp :: FL Prim C(a b) -> RL Prim C(c a) -> Sealed (FL Prim C(c))+ sfp sofar NilRL = seal sofar+ sfp sofar (p:<:ps)+ | is_hunk p || is_binary p+ = case commuteFL (p :> sofar) of+ Right (sofar' :> _) -> sfp sofar' ps+ Left _ -> sfp (p:>:sofar) ps+ sfp sofar (p:<:ps) = sfp (p:>:sofar) ps++get_unrecorded_no_look_for_adds :: RepoPatch p => Repository p C(r u t) -> [FileName] -> IO (FL Prim C(r y))+get_unrecorded_no_look_for_adds r paths = get_unrecorded_private (filter (/= LookForAdds)) r paths ++get_unrecorded_unsorted :: RepoPatch p => Repository p C(r u t) -> IO (FL Prim C(r u))+get_unrecorded_unsorted r = get_unrecorded_in_files_unsorted r []++get_unrecorded :: RepoPatch p => Repository p C(r u t) -> IO (FL Prim C(r u))+get_unrecorded r = get_unrecorded_private id r []++-- | Gets the unrecorded changes in the given paths in the current repository,+-- without sorting them for presentation to the user+get_unrecorded_in_files_unsorted :: RepoPatch p => Repository p C(r u t) -> [FileName] -> IO (FL Prim C(r u))+get_unrecorded_in_files_unsorted = get_unrecorded_private (AnyOrder:)++-- | Gets the unrecorded changes in the given paths in the current repository.+get_unrecorded_in_files :: RepoPatch p => Repository p C(r u t) -> [FileName] -> IO (FL Prim C(r u))+get_unrecorded_in_files = get_unrecorded_private id ++-- | The /unrecorded/ includes the pending and the working directory changes.+-- The third argument is a list of paths: if this list is [], it will diff+-- the whole repo, but if there are elements in it, the function will return+-- only changes to files under those paths. The paths must be fixed paths +-- starting with ".", but need not yet be unique.+get_unrecorded_private :: RepoPatch p => ([DarcsFlag]->[DarcsFlag]) -> Repository p C(r u t) -> [FileName] -> IO (FL Prim C(r y))+get_unrecorded_private _ (Repo _ opts _ _) _+ | NoUpdateWorking `elem` opts = return $ unsafeCoerceP NilFL+get_unrecorded_private modopts repository@(Repo r oldopts _ _) files =+ withCurrentDirectory r (do+ debugMessage "Looking for unrecorded changes..."+ cur <- slurp_pending repository+ work <- if LookForAdds `elem` opts+ then do nboring <- if Boring `elem` opts+ then return $ darcsdir_filter+ else boring_file_filter+ slurp_unboring (myfilt cur nboring) "."+ else co_slurp cur "."+ ftf <- filetype_function+ Sealed pend <- read_pending repository+ let changed_files = apply_to_filepaths pend filesFP+ pre_changed_files = apply_to_filepaths (invert pend) filesFP+ Sealed relevantPend <- return $ if null files+ then seal pend+ else choose_touching pre_changed_files pend+ debugMessage "diffing dir..."+ let diffs = if null files+ then unsafeDiff opts ftf cur work+ else unsafeDiffAtPaths (ignoreTimes, lookForAdds, summary) ftf cur work changed_files+ dif = if AnyOrder `elem` opts+ then relevantPend +>+ diffs+ else sort_coalesceFL $ relevantPend +>+ diffs+ seq dif $ debugMessage "Found unrecorded changes."+ return dif)+ where myfilt s nboring f = slurp_has f s || nboring [f] /= []+ opts = modopts oldopts+ -- NoSummary/Summary both present gives False+ -- Just Summary gives True+ -- Just NoSummary gives False+ -- Neither gives False+ summary = Summary `elem` opts && NoSummary `notElem` opts+ lookForAdds = LookForAdds `elem` opts+ ignoreTimes = IgnoreTimes `elem` opts+ filesFP = map fn2fp files++-- @todo: we should not have to open the result of HashedRepo and+-- seal it. Instead, update this function to work with type witnesses+-- by fixing DarcsRepo to match HashedRepo in the handling of+-- Repository state.+read_repo :: RepoPatch p => Repository p C(r u t) -> IO (PatchSet p C(r))+read_repo repo@(Repo r opts rf _)+ | format_has HashedInventory rf = do ps <- HashedRepo.read_repo repo r+ return ps+ | otherwise = do Sealed ps <- DarcsRepo.read_repo opts r+ return $ unsafeCoerceP ps++readTentativeRepo :: RepoPatch p => Repository p C(r u t) -> IO (PatchSet p C(t))+readTentativeRepo repo@(Repo r opts rf _)+ | format_has HashedInventory rf = do ps <- HashedRepo.read_tentative_repo repo r+ return ps+ | otherwise = do Sealed ps <- DarcsRepo.read_tentative_repo opts r+ return $ unsafeCoerceP ps++makePatchLazy :: RepoPatch p => Repository p C(r u t) -> PatchInfoAnd p C(x y) -> IO (PatchInfoAnd p C(x y))+makePatchLazy (Repo r opts rf (DarcsRepository _ c)) p+ | format_has HashedInventory rf = withCurrentDirectory r $ HashedRepo.write_and_read_patch c (compression opts) p+ | otherwise = withCurrentDirectory r $ DarcsRepo.write_and_read_patch opts p++sync_repo :: Repository p C(r u t) -> IO ()+sync_repo (Repo r _ rf (DarcsRepository _ c))+ | format_has HashedInventory rf = withCurrentDirectory r $ HashedRepo.sync_repo c+sync_repo (Repo r _ _ (DarcsRepository p _)) = withCurrentDirectory r $ syncPristine p++prefsUrl :: Repository p C(r u t) -> String+prefsUrl (Repo r _ _ (DarcsRepository _ _)) = r ++ "/"++darcsdir++"/prefs"++unrevertUrl :: Repository p C(r u t) -> String+unrevertUrl (Repo r _ _ (DarcsRepository _ _)) = r ++ "/"++darcsdir++"/patches/unrevert"++applyToWorking :: Patchy p => Repository p1 C(r u t) -> [DarcsFlag] -> p C(u y) -> IO ()+applyToWorking (Repo r _ _ (DarcsRepository _ _)) opts patch =+ withCurrentDirectory r $ if Quiet `elem` opts+ then runSilently $ apply opts patch+ else runTolerantly $ apply opts patch++handle_pend_for_add :: forall p q C(r u t x y). (RepoPatch p, Effect q)+ => Repository p C(r u t) -> q C(x y) -> IO ()+handle_pend_for_add (Repo _ opts _ _) _ | NoUpdateWorking `elem` opts = return ()+handle_pend_for_add (Repo _ _ _ rt) p =+ do let pn = pendingName rt ++ ".tentative"+ Sealed pend <- (readPrims `fmap` gzReadFilePS pn) `catchall` (return $ Sealed NilFL)+ let effectp = if allFL is_simple pend then crude_sift $ effect p+ else effect p+ Sealed newpend <- return $ rmpend (progressFL "Removing from pending:" effectp) pend+ writePatch pn $ fromPrims_ newpend+ where rmpend :: FL Prim C(a b) -> FL Prim C(a c) -> Sealed (FL Prim C(b))+ rmpend NilFL x = Sealed x+ rmpend _ NilFL = Sealed NilFL+ rmpend (x:>:xs) xys | Just ys <- removeFL x xys = rmpend xs ys+ rmpend (x:>:xs) ys =+ case commuteWhatWeCanFL (x:>xs) of+ a:>x':>b -> case rmpend a ys of+ Sealed ys' -> case commute (invert (x':>:b) :> ys') of+ Just (ys'' :> _) -> seal ys''+ Nothing -> seal $ invert (x':>:b)+>+ys'+ -- DJR: I don't think this+ -- last case should be+ -- reached, but it also+ -- shouldn't lead to+ -- corruption.+ fromPrims_ :: FL Prim C(a b) -> Patch C(a b)+ fromPrims_ = fromPrims++is_simple :: Prim C(x y) -> Bool+is_simple x = is_hunk x || is_binary x || is_setpref x++crude_sift :: FL Prim C(x y) -> FL Prim C(x y)+crude_sift xs = if allFL is_simple xs then filterFL ishunkbinary xs else xs+ where ishunkbinary :: Prim C(x y) -> EqCheck C(x y)+ ishunkbinary x | is_hunk x || is_binary x = unsafeCoerceP IsEq+ | otherwise = NotEq++data HashedVsOld a = HvsO { old, hashed :: a }++decideHashedOrNormal :: Monad m => RepoFormat -> HashedVsOld (m a) -> m a+decideHashedOrNormal rf (HvsO { hashed = h, old = o })+ | format_has HashedInventory rf = h+ | otherwise = o+++tentativelyMergePatches :: RepoPatch p+ => Repository p C(r u t) -> String -> [DarcsFlag]+ -> FL (PatchInfoAnd p) C(x r) -> FL (PatchInfoAnd p) C(x y)+ -> IO (Sealed (FL Prim C(u)))+tentativelyMergePatches = tentativelyMergePatches_ MakeChanges++considerMergeToWorking :: RepoPatch p+ => Repository p C(r u t) -> String -> [DarcsFlag]+ -> FL (PatchInfoAnd p) C(x r) -> FL (PatchInfoAnd p) C(x y)+ -> IO (Sealed (FL Prim C(u)))+considerMergeToWorking = tentativelyMergePatches_ DontMakeChanges++data MakeChanges = MakeChanges | DontMakeChanges deriving ( Eq )++tentativelyMergePatches_ :: forall p C(r u t y x). RepoPatch p+ => MakeChanges+ -> Repository p C(r u t) -> String -> [DarcsFlag]+ -> FL (PatchInfoAnd p) C(x r) -> FL (PatchInfoAnd p) C(x y)+ -> IO (Sealed (FL Prim C(u)))+tentativelyMergePatches_ mc r cmd opts usi themi =+ do let us = mapFL_FL hopefully usi+ them = mapFL_FL hopefully themi+ _ :/\: pc <- return $ merge (progressFL "Merging them" them :\/: progressFL "Merging us" us)+ pend <- get_unrecorded_unsorted r -- we don't care if it looks pretty...+ anonpend <- anonymous (fromPrims pend)+ pend' :/\: pw <- return $ merge (pc :\/: anonpend :>: NilFL)+ let pwprim = joinPatches $ progressFL "Examining patches for conflicts" $ mapFL_FL patchcontents pw+ Sealed standard_resolved_pw <- return $ standard_resolution pwprim+ debugMessage "Checking for conflicts..."+ mapM_ backupByCopying $ list_touched_files standard_resolved_pw+ debugMessage "Announcing conflicts..."+ have_conflicts <- announce_merge_conflicts cmd opts standard_resolved_pw+ debugMessage "Checking for unrecorded conflicts..."+ have_unrecorded_conflicts <- check_unrecorded_conflicts opts pc+ debugMessage "Reading working directory..."+ (_, working) <- slurp_recorded_and_unrecorded r+ debugMessage "Working out conflicts in actual working directory..."+ Sealed pw_resolution <-+ case (want_external_merge opts, have_conflicts || have_unrecorded_conflicts) of+ (Nothing,_) -> return $ if AllowConflicts `elem` opts+ then seal NilFL+ else seal standard_resolved_pw+ (_,False) -> return $ seal standard_resolved_pw+ (Just c, True) -> external_resolution working c+ (effect us +>+ pend)+ (effect them) pwprim+ debugMessage "Applying patches to the local directories..."+ when (mc == MakeChanges) $+ do let doChanges :: FL (PatchInfoAnd p) C(x r) -> IO ()+ doChanges NilFL = applyps r themi+ doChanges _ = applyps r (mapFL_FL n2pia pc)+ doChanges usi+ setTentativePending r (effect pend' +>+ pw_resolution)+ return $ seal (effect pwprim +>+ pw_resolution)+ where mapAdd :: Repository p C(i l m) -> FL (PatchInfoAnd p) C(i j) -> [IO ()]+ mapAdd _ NilFL = []+ mapAdd r'@(Repo dir df rf dr) (a:>:as) =+ -- we construct a new Repository object on the recursive case so that the+ -- recordedstate of the repository can match the fact that we just wrote a patch+ tentativelyAddPatch_ DontUpdatePristine r' opts a : mapAdd (Repo dir df rf dr) as+ applyps :: Repository p C(i l m) -> FL (PatchInfoAnd p) C(i j) -> IO ()+ applyps repo ps = do debugMessage "Adding patches to inventory..."+ sequence_ $ mapAdd repo ps+ debugMessage "Applying patches to pristine..."+ applyToTentativePristine repo ps++announce_merge_conflicts :: String -> [DarcsFlag] -> FL Prim C(x y) -> IO Bool+announce_merge_conflicts cmd opts resolved_pw =+ case nubsort $ list_touched_files $ resolved_pw of+ [] -> return False+ cfs -> if MarkConflicts `elem` opts || AllowConflicts `elem` opts+ || want_external_merge opts /= Nothing+ then do putStrLn "We have conflicts in the following files:"+ putStrLn $ unwords cfs+ return True+ else do putStrLn "There are conflicts in the following files:"+ putStrLn $ unwords cfs+ fail $ "Refusing to "++cmd++" patches leading to conflicts.\n"+++ "If you would rather apply the patch and mark the conflicts,\n"+++ "use the --mark-conflicts or --allow-conflicts options to "++cmd++"\n"+++ "These can set as defaults by adding\n"+++ " "++cmd++" mark-conflicts\n"+++ "to "++darcsdir++"/prefs/defaults in the target repo. "++check_unrecorded_conflicts :: forall p C(r y). RepoPatch p => [DarcsFlag] -> FL (Named p) C(r y) -> IO Bool+check_unrecorded_conflicts opts _ | NoUpdateWorking `elem` opts = return False+check_unrecorded_conflicts opts pc =+ do repository <- identifyDarcs1Repository opts "."+ cuc repository+ where cuc :: Repository Patch C(r u t) -> IO Bool+ cuc r = do Sealed mpend <- read_pending r :: IO (Sealed (FL Prim C(r)))+ case mpend of+ NilFL -> return False+ pend ->+ case merge (fromPrims_ pend :\/: fromPrims_ (concatFL $ mapFL_FL effect pc)) of+ _ :/\: pend' ->+ case list_conflicted_files pend' of+ [] -> return False+ fs -> do yorn <- promptYorn+ ("You have conflicting local changes to:\n"+ ++ unwords fs++"\nProceed?")+ when (yorn /= 'y') $+ do putStrLn "Cancelled."+ exitWith ExitSuccess+ return True+ fromPrims_ :: FL Prim C(a b) -> p C(a b)+ fromPrims_ = fromPrims++tentativelyAddPatch :: RepoPatch p+ => Repository p C(r u t) -> [DarcsFlag] -> PatchInfoAnd p C(r y) -> IO ()+tentativelyAddPatch = tentativelyAddPatch_ UpdatePristine++data UpdatePristine = UpdatePristine | DontUpdatePristine deriving Eq++tentativelyAddPatch_ :: RepoPatch p+ => UpdatePristine -> Repository p C(r u t) -> [DarcsFlag]+ -> PatchInfoAnd p C(r y) -> IO ()+tentativelyAddPatch_ _ _ opts _+ | DryRun `elem` opts = bug "tentativelyAddPatch_ called when --dry-run is specified"+tentativelyAddPatch_ up r@(Repo dir _ rf (DarcsRepository _ c)) opts p =+ withCurrentDirectory dir $+ do decideHashedOrNormal rf $ HvsO {+ hashed = HashedRepo.add_to_tentative_inventory c (compression opts) p,+ old = DarcsRepo.add_to_tentative_inventory opts (hopefully p) }+ when (up == UpdatePristine) $ do debugMessage "Applying to pristine cache..."+ applyToTentativePristine r p+ debugMessage "Updating pending..."+ handle_pend_for_add r p++applyToTentativePristine :: (Effect q, Patchy q) => Repository p C(r u t) -> q C(r y) -> IO ()+applyToTentativePristine (Repo dir opts rf (DarcsRepository _ c)) p =+ withCurrentDirectory dir $+ do when (Verbose `elem` opts) $ putDocLn $ text "Applying to pristine..." <+> description p+ decideHashedOrNormal rf $ HvsO {hashed = HashedRepo.apply_to_tentative_pristine c opts p,+ old = DarcsRepo.add_to_tentative_pristine p}++-- | This fuction is unsafe because it accepts a patch that works on the tentative+-- pending and we don't currently track the state of the tentative pending.+tentativelyAddToPending :: forall p C(r u t x y). RepoPatch p+ => Repository p C(r u t) -> [DarcsFlag] -> FL Prim C(x y) -> IO ()+tentativelyAddToPending (Repo _ opts _ _) _ _+ | NoUpdateWorking `elem` opts = return ()+ | DryRun `elem` opts = bug "tentativelyAddToPending called when --dry-run is specified"+tentativelyAddToPending (Repo dir _ _ rt) _ patch =+ withCurrentDirectory dir $ do+ let pn = pendingName rt+ tpn = pn ++ ".tentative"+ Sealed pend <- readPrims `liftM` (gzReadFilePS tpn `catchall` (return B.empty))+ FlippedSeal newpend_ <- return $ newpend (unsafeCoerceP pend :: FL Prim C(a x)) patch+ writePatch tpn $ fromPrims_ newpend_+ where newpend :: FL Prim C(a b) -> FL Prim C(b c) -> FlippedSeal (FL Prim) C(c)+ newpend NilFL patch_ = flipSeal patch_+ newpend p patch_ = flipSeal $ p +>+ patch_+ fromPrims_ :: FL Prim C(a b) -> Patch C(a b)+ fromPrims_ = fromPrims++-- | setTentativePending is basically unsafe. It overwrites the pending state with a new one, not related to+-- the repository state.+setTentativePending :: forall p C(r u t x y). RepoPatch p => Repository p C(r u t) -> FL Prim C(x y) -> IO ()+setTentativePending (Repo _ opts _ _) _ | NoUpdateWorking `elem` opts = return ()+setTentativePending (Repo dir _ _ rt) patch = do+ Sealed prims <- return $ sift_for_pending patch+ withCurrentDirectory dir $+ writePatch (pendingName rt ++ ".tentative") $ fromPrims_ prims+ where fromPrims_ :: FL Prim C(a b) -> Patch C(a b)+ fromPrims_ = fromPrims++-- | prepend is basically unsafe. It overwrites the pending state+-- with a new one, not related to the repository state.+prepend :: forall p C(r u t x y). RepoPatch p =>+ Repository p C(r u t) -> FL Prim C(x y) -> IO ()+prepend (Repo _ opts _ _) _ | NoUpdateWorking `elem` opts = return ()+prepend (Repo _ _ _ rt) patch =+ do let pn = pendingName rt ++ ".tentative"+ Sealed pend <- readPrims `liftM` (gzReadFilePS pn `catchall` (return B.empty))+ Sealed newpend_ <- return $ newpend pend patch+ writePatch pn $ fromPrims_ (crude_sift newpend_)+ where newpend :: FL Prim C(b c) -> FL Prim C(a b) -> Sealed (FL Prim C(a))+ newpend NilFL patch_ = seal patch_+ newpend p patch_ = seal $ patch_ +>+ p+ fromPrims_ :: FL Prim C(a b) -> Patch C(a b)+ fromPrims_ = fromPrims++tentativelyRemovePatches :: RepoPatch p => Repository p C(r u t) -> [DarcsFlag]+ -> FL (Named p) C(x t) -> IO ()+tentativelyRemovePatches = tentativelyRemovePatches_ UpdatePristine++tentativelyRemovePatches_ :: forall p C(r u t x). RepoPatch p => UpdatePristine+ -> Repository p C(r u t) -> [DarcsFlag]+ -> FL (Named p) C(x t) -> IO ()+tentativelyRemovePatches_ up repository@(Repo dir _ rf (DarcsRepository _ c)) opts ps =+ withCurrentDirectory dir $ do+ when (up == UpdatePristine) $ do debugMessage "Adding changes to pending..."+ prepend repository $ effect ps+ remove_from_unrevert_context repository ps+ debugMessage "Removing changes from tentative inventory..."+ if format_has HashedInventory rf+ then do HashedRepo.remove_from_tentative_inventory repository (compression opts) ps+ when (up == UpdatePristine) $+ HashedRepo.apply_to_tentative_pristine c opts $+ progressFL "Applying inverse to pristine" $ invert ps+ else DarcsRepo.remove_from_tentative_inventory (up==UpdatePristine) opts ps++tentativelyReplacePatches :: forall p C(r u t x). RepoPatch p => Repository p C(r u t) -> [DarcsFlag]+ -> FL (Named p) C(x t) -> IO ()+tentativelyReplacePatches repository@(Repo x y z w) opts ps =+ -- tentativelyRemovePatches_ leaves the repository in state C(x u t)+ do tentativelyRemovePatches_ DontUpdatePristine repository opts ps+ -- Now we add the patches back so that the repo again has state C(r u t)+ sequence_ $ mapAdd ((Repo x y z w) :: Repository p C(x u t)) ps+ where mapAdd :: Repository p C(i l m) -> FL (Named p) C(i j) -> [IO ()]+ mapAdd _ NilFL = []+ mapAdd r@(Repo dir df rf dr) (a:>:as) =+ -- we construct a new Repository object on the recursive case so that the+ -- recordedstate of the repository can match the fact that we just wrote a patch+ tentativelyAddPatch_ DontUpdatePristine r opts (n2pia a) : mapAdd (Repo dir df rf dr) as++finalize_pending :: RepoPatch p => Repository p C(r u t) -> IO ()+finalize_pending (Repo dir opts _ rt)+ | NoUpdateWorking `elem` opts =+ withCurrentDirectory dir $ removeFileMayNotExist $ (pendingName rt)+finalize_pending repository@(Repo dir _ _ rt) = do+ withCurrentDirectory dir $ do let pn = pendingName rt+ tpn = pn ++ ".tentative"+ tpfile <- gzReadFilePS tpn `catchall` (return B.empty)+ Sealed tpend <- return $ readPrims tpfile+ Sealed new_pending <- return $ sift_for_pending tpend+ make_new_pending repository new_pending++finalizeRepositoryChanges :: RepoPatch p => Repository p C(r u t) -> IO ()+finalizeRepositoryChanges (Repo _ opts _ _)+ | DryRun `elem` opts = bug "finalizeRepositoryChanges called when --dry-run specified"+finalizeRepositoryChanges repository@(Repo dir opts rf _)+ | format_has HashedInventory rf =+ withCurrentDirectory dir $ do debugMessage "Considering whether to test..."+ testTentative repository+ debugMessage "Finalizing changes..."+ withSignalsBlocked $ do HashedRepo.finalize_tentative_changes repository (compression opts)+ finalize_pending repository+ debugMessage "Done finalizing changes..."+finalizeRepositoryChanges repository@(Repo dir _ _ (DarcsRepository _ _)) =+ withCurrentDirectory dir $ do debugMessage "Considering whether to test..."+ testTentative repository+ debugMessage "Finalizing changes..."+ withSignalsBlocked $ do DarcsRepo.finalize_pristine_changes+ DarcsRepo.finalize_tentative_changes+ finalize_pending repository++testTentative :: RepoPatch p => Repository p C(r u t) -> IO ()+testTentative = testAny withTentative++testRecorded :: RepoPatch p => Repository p C(r u t) -> IO ()+testRecorded = testAny withRecorded++testAny :: RepoPatch p => (Repository p C(r u t)+ -> ((AbsolutePath -> IO ()) -> IO ())+ -> (AbsolutePath -> IO ()) -> IO ())+ -> Repository p C(r u t) -> IO ()+testAny withD repository@(Repo dir opts _ _) =+ when (Test `elem` opts) $ withCurrentDirectory dir $+ do let putInfo = if not $ Quiet `elem` opts then putStrLn else const (return ())+ debugMessage "About to run test if it exists."+ testline <- get_prefval "test"+ case testline of+ Nothing -> return ()+ Just testcode ->+ withD repository (wd "testing") $ \_ ->+ do putInfo "Running test...\n"+ when (SetScriptsExecutable `elem` opts) setScriptsExecutable+ ec <- system testcode+ if ec == ExitSuccess+ then putInfo "Test ran successfully.\n"+ else do putInfo "Test failed!\n"+ exitWith ec+ where wd = if LeaveTestDir `elem` opts then withPermDir else withTempDir++revertRepositoryChanges :: RepoPatch p => Repository p C(r u t) -> IO ()+revertRepositoryChanges (Repo _ opts _ _)+ | DryRun `elem` opts = bug "revertRepositoryChanges called when --dry-run is specified"+revertRepositoryChanges r@(Repo dir opts rf dr@(DarcsRepository _ _)) =+ withCurrentDirectory dir $+ do removeFileMayNotExist (pendingName dr ++ ".tentative")+ Sealed x <- read_pending r+ setTentativePending r $ effect x+ when (NoUpdateWorking `elem` opts) $ removeFileMayNotExist $ pendingName dr+ decideHashedOrNormal rf $ HvsO { hashed = HashedRepo.revert_tentative_changes,+ old = DarcsRepo.revert_tentative_changes }++patchSetToPatches :: RepoPatch p => RL (RL (PatchInfoAnd p)) C(x y) -> FL (Named p) C(x y)+patchSetToPatches patchSet = mapFL_FL hopefully $ reverseRL $ concatRL patchSet++getUMask :: [DarcsFlag] -> Maybe String+getUMask [] = Nothing+getUMask ((UMask u):_) = Just u+getUMask (_:l) = getUMask l++withGutsOf :: Repository p C(r u t) -> IO () -> IO ()+withGutsOf (Repo _ _ rf _) | format_has HashedInventory rf = id+ | otherwise = withSignalsBlocked++withRepository :: [DarcsFlag] -> (forall p C(r u). RepoPatch p => Repository p C(r u r) -> IO a) -> IO a+withRepository opts1 = withRepositoryDirectory opts1 "."++withRepositoryDirectory :: forall a. [DarcsFlag] -> String+ -> (forall p C(r u). RepoPatch p => Repository p C(r u r) -> IO a) -> IO a+withRepositoryDirectory opts1 url job =+ do Repo dir opts rf rt <- identifyDarcs1Repository opts1 url+ let rt' = case rt of DarcsRepository t c -> DarcsRepository t c+ if format_has Darcs2 rf+ then do debugMessage $ "Identified darcs-2 repo: " ++ dir+ job1_ (Repo dir opts rf rt')+ else do debugMessage $ "Identified darcs-1 repo: " ++ dir+ job2_ (Repo dir opts rf rt)+ where job1_ :: Repository (FL RealPatch) C(r u r) -> IO a+ job1_ = job+ job2_ :: Repository Patch C(r u r) -> IO a+ job2_ = job+++-- RankNTypes+-- $- works around the lack of impredicative instantiation in GHC+($-) ::((forall p C(r u). RepoPatch p => Repository p C(r u r) -> IO a) -> IO a)+ -> (forall p C(r u). RepoPatch p => Repository p C(r u r) -> IO a) -> IO a+x $- y = x y++withRepoLock :: [DarcsFlag] -> (forall p C(r u). RepoPatch p => Repository p C(r u r) -> IO a) -> IO a+withRepoLock opts job =+ withRepository opts $- \repository@(Repo _ _ rf _) ->+ do case write_problem rf of+ Nothing -> return ()+ Just err -> fail err+ let name = "./"++darcsdir++"/lock"+ wu = case (getUMask opts) of+ Nothing -> id+ Just u -> withUMask u+ wu $ if DryRun `elem` opts+ then job repository+ else withLock name (revertRepositoryChanges repository >> job repository)++withRepoReadLock :: [DarcsFlag] -> (forall p C(r u). RepoPatch p => Repository p C(r u r) -> IO a) -> IO a+withRepoReadLock opts job =+ withRepository opts $- \repository@(Repo _ _ rf _) ->+ do case write_problem rf of+ Nothing -> return ()+ Just err -> fail err+ let name = "./"++darcsdir++"/lock"+ wu = case (getUMask opts) of Nothing -> id+ Just u -> withUMask u+ wu $ if format_has HashedInventory rf || DryRun `elem` opts+ then job repository+ else withLock name (revertRepositoryChanges repository >> job repository)++remove_from_unrevert_context :: forall p C(r u t x). RepoPatch p+ => Repository p C(r u t) -> FL (Named p) C(x t) -> IO ()+remove_from_unrevert_context repository ps = do+ Sealed bundle <- unrevert_patch_bundle `catchall` (return $ seal (NilRL:<:NilRL))+ remove_from_unrevert_context_ bundle+ where unrevert_impossible unrevert_loc =+ do yorn <- promptYorn "This operation will make unrevert impossible!\nProceed?"+ case yorn of+ 'n' -> fail "Cancelled."+ 'y' -> removeFile unrevert_loc `catchall` return ()+ _ -> impossible+ pis = mapFL patch2patchinfo ps+ unrevert_patch_bundle :: IO (SealedPatchSet p)+ unrevert_patch_bundle = do pf <- B.readFile (unrevertUrl repository)+ case scan_bundle pf of+ Right foo -> return foo+ Left err -> fail $ "Couldn't parse unrevert patch:\n" ++ err+ remove_from_unrevert_context_ :: PatchSet p C(z) -> IO ()+ remove_from_unrevert_context_ (NilRL :<: NilRL) = return ()+ remove_from_unrevert_context_ bundle = do+ let unrevert_loc = unrevertUrl repository+ debugMessage "Adjusting the context of the unrevert changes..."+ ref <- readTentativeRepo repository+ case get_common_and_uncommon (bundle, ref) of+ (common,(h_us:<:NilRL):<:NilRL :\/: NilRL:<:NilRL) ->+ case commuteRL (reverseFL ps :> hopefully h_us) of+ Nothing -> unrevert_impossible unrevert_loc+ Just (us' :> _) -> do+ s <- slurp_recorded repository+ writeDocBinFile unrevert_loc $+ make_bundle [] s+ (common \\ pis) (us':>:NilFL)+ (common,(x:<:NilRL):<:NilRL:\/:_)+ | isr && any (`elem` common) pis -> unrevert_impossible unrevert_loc+ | isr -> return ()+ where isr = isJust $ hopefullyM x+ _ -> unrevert_impossible unrevert_loc++-- | Writes out a fresh copy of the inventory that minimizes the+-- amount of inventory that need be downloaded when people pull from+-- the repository.+--+-- Specifically, it breaks up the inventory on the most recent tag.+-- This speeds up most commands when run remotely, both because a+-- smaller file needs to be transfered (only the most recent+-- inventory). It also gives a guarantee that all the patches prior+-- to a given tag are included in that tag, so less commutation and+-- history traversal is needed. This latter issue can become very+-- important in large repositories.+optimizeInventory :: RepoPatch p => Repository p C(r u t) -> IO ()+optimizeInventory repository@(Repo r opts rf (DarcsRepository _ c)) =+ do ps <- read_repo repository+ decideHashedOrNormal rf $+ HvsO { hashed = do revertRepositoryChanges repository+ HashedRepo.write_tentative_inventory c (compression opts) $ deep_optimize_patchset ps+ finalizeRepositoryChanges repository,+ old = DarcsRepo.write_inventory r $ deep_optimize_patchset ps+ }++cleanRepository :: RepoPatch p => Repository p C(r u t) -> IO ()+cleanRepository repository@(Repo _ _ rf _) =+ decideHashedOrNormal rf $+ HvsO { hashed = HashedRepo.clean_pristine repository,+ old = return () }++replacePristineFromSlurpy :: Repository p C(r u t) -> Slurpy -> IO ()+replacePristineFromSlurpy (Repo r opts rf (DarcsRepository pris c)) s+ | format_has HashedInventory rf = withCurrentDirectory r $ HashedRepo.replacePristineFromSlurpy c (compression opts) s+ | otherwise = withCurrentDirectory r $ Pristine.replacePristineFromSlurpy s pris++createPristineDirectoryTree :: RepoPatch p => Repository p C(r u t) -> FilePath -> IO ()+createPristineDirectoryTree repo@(Repo r opts rf (DarcsRepository pris c)) reldir+ | format_has HashedInventory rf =+ do createDirectoryIfMissing True reldir+ withCurrentDirectory reldir $ HashedRepo.copy_pristine c (compression opts) r (darcsdir++"/hashed_inventory")+ | otherwise =+ do dir <- toPath `fmap` ioAbsoluteOrRemote reldir+ done <- withCurrentDirectory r $ easyCreatePristineDirectoryTree pris dir+ unless done $ do Sealed patches <- (seal . reverseRL . concatRL) `liftM` read_repo repo+ createDirectoryIfMissing True dir+ withCurrentDirectory dir $ apply_patches [] patches++-- fp below really should be FileName+createPartialsPristineDirectoryTree :: (FilePathLike fp, RepoPatch p) => Repository p C(r u t) -> [fp] -> FilePath -> IO ()+createPartialsPristineDirectoryTree (Repo r opts rf (DarcsRepository _ c)) prefs dir+ | format_has HashedInventory rf =+ do createDirectoryIfMissing True dir+ withCurrentDirectory dir $+ HashedRepo.copy_partials_pristine c (compression opts) r (darcsdir++"/hashed_inventory") prefs+createPartialsPristineDirectoryTree r@(Repo rdir _ _ (DarcsRepository pris _)) prefs dir+ = withCurrentDirectory rdir $+ do done <- easyCreatePartialsPristineDirectoryTree prefs pris dir+ unless done $ withRecorded r (withTempDir "recorded") $ \_ -> do+ clonePartialsTree "." dir (map toFilePath prefs)++pristineFromWorking :: RepoPatch p => Repository p C(r u t) -> IO ()+pristineFromWorking (Repo dir opts rf (DarcsRepository _ c))+ | format_has HashedInventory rf =+ withCurrentDirectory dir $ HashedRepo.pristine_from_working c (compression opts)+pristineFromWorking (Repo dir _ _ (DarcsRepository p _)) =+ withCurrentDirectory dir $ createPristineFromWorking p++withRecorded :: RepoPatch p => Repository p C(r u t)+ -> ((AbsolutePath -> IO a) -> IO a) -> (AbsolutePath -> IO a) -> IO a+withRecorded repository mk_dir f+ = mk_dir $ \d -> do createPristineDirectoryTree repository (toFilePath d)+ f d++checkPristineAgainstSlurpy :: RepoPatch p => Repository p C(r u t) -> Slurpy -> IO Bool+checkPristineAgainstSlurpy repository@(Repo _ opts _ _) s2 =+ do s1 <- slurp_recorded repository+ ftf <- filetype_function+ -- The @$!@ is necessary because some code called from this function uses+ -- unsafeInterleaveIO around functions that throw exceptions. If one used+ -- @$@ instead of @$!@ here, those exceptions might not be caught by code+ -- that runs this function inside a @catch@.+ return $! nullFL $ unsafeDiff (LookForAdds:IgnoreTimes:opts) ftf s1 s2++withTentative :: forall p a C(r u t). RepoPatch p =>+ Repository p C(r u t) -> ((AbsolutePath -> IO a) -> IO a)+ -> (AbsolutePath -> IO a) -> IO a+withTentative (Repo dir opts rf (DarcsRepository _ c)) mk_dir f+ | format_has HashedInventory rf =+ mk_dir $ \d -> do HashedRepo.copy_pristine c (compression opts) dir (darcsdir++"/tentative_pristine")+ f d+withTentative repository@(Repo dir opts _ _) mk_dir f =+ withRecorded repository mk_dir $ \d ->+ do Sealed ps <- read_patches (dir ++ "/"++darcsdir++"/tentative_pristine")+ apply opts $ joinPatches ps+ f d+ where read_patches :: FilePath -> IO (Sealed (FL p C(x)))+ read_patches fil = do ps <- B.readFile fil+ return $ case readPatch ps of+ Just (x, _) -> x+ Nothing -> seal NilFL++getMarkedupFile :: RepoPatch p => Repository p C(r u t) -> PatchInfo -> FilePath -> IO MarkedUpFile+getMarkedupFile repository pinfo f = do+ Sealed (FlippedSeal patches) <- (seal . dropWhileFL ((/= pinfo) . info)+ . reverseRL . concatRL) `liftM` read_repo repository+ return $ snd $ do_mark_all patches (f, empty_markedup_file)+ where dropWhileFL :: (FORALL(x y) a C(x y) -> Bool) -> FL a C(r v) -> FlippedSeal (FL a) C(v)+ dropWhileFL _ NilFL = flipSeal NilFL+ dropWhileFL p xs@(x:>:xs')+ | p x = dropWhileFL p xs'+ | otherwise = flipSeal xs+do_mark_all :: RepoPatch p => FL (PatchInfoAnd p) C(x y)+ -> (FilePath, MarkedUpFile) -> (FilePath, MarkedUpFile)+do_mark_all (hp:>:pps) (f, mk) =+ case hopefullyM hp of+ Just p -> do_mark_all pps $ markup_file (info hp) (patchcontents p) (f, mk)+ Nothing -> (f, [(BC.pack "Error reading a patch!",None)])+do_mark_all NilFL (f, mk) = (f, mk)++-- | Sets scripts in or below the current directory executable. A script is any file that starts+-- with the bytes '#!'. This is used sometimes for --set-scripts-executable, but at other times+-- --set-scripts-executable is handled by the hunk patch case of applyFL.+setScriptsExecutable :: IO ()+setScriptsExecutable = do+ debugMessage "Making scripts executable"+ myname <- getCurrentDirectory+ c <- list_slurpy_files `fmap` (HashedRepo.slurp_all_but_darcs myname)+ let setExecutableIfScript f =+ do contents <- B.readFile f+ when (BC.pack "#!" `B.isPrefixOf` contents) $ do+ debugMessage ("Making executable: " ++ f)+ setExecutable f True+ mapM_ setExecutableIfScript c
@@ -0,0 +1,42 @@+-- Copyright (C) 2006-2007 David Roundy+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; if not, write to the Free Software Foundation,+-- Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.++{-# OPTIONS_GHC -cpp -fglasgow-exts #-}+{-# LANGUAGE CPP #-}+-- , KindSignatures #-}++#include "gadts.h"++module Darcs.Repository.InternalTypes ( Repository(..), RepoType(..), Pristine(..)+ , extractCache+ ) where++import Darcs.Repository.Cache ( Cache )+import Darcs.Flags ( DarcsFlag )+import Darcs.Repository.Format ( RepoFormat )++data Pristine+ = NoPristine !String+ | PlainPristine !String+ | HashedPristine+ deriving ( Show )++data Repository (p :: * C(-> * -> *)) C(recordedstate unrecordedstate tentativestate) = Repo !String ![DarcsFlag] !RepoFormat !(RepoType p) deriving ( Show )++data RepoType (p :: * C(-> * -> *)) = DarcsRepository !Pristine Cache deriving ( Show )++extractCache :: Repository p C(r u t) -> Cache+extractCache (Repo _ _ _ (DarcsRepository _ c)) = c
@@ -0,0 +1,49 @@+% Copyright (C) 2002-2004 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.+++\begin{code}+module Darcs.Repository.Motd (get_motd, show_motd) where+import Control.Monad ( unless )+import Darcs.Flags ( DarcsFlag( Quiet, XMLOutput ) )+import Darcs.External ( fetchFilePS, Cachable(..) )+import Darcs.Global ( darcsdir )+import qualified Data.ByteString as B (null, hPut, empty, ByteString)+import Darcs.Utils ( catchall )+import System.IO ( stdout )+\end{code}++\paragraph{motd}\label{motd}+The \verb!_darcs/prefs/motd! file may contain a ``message of the day''+which will be displayed to users who get or pull from the repository without the+\verb!--quiet! option.++\begin{code}+-- | Fetch and return the message of the day for a given repository.+get_motd :: String -> IO B.ByteString+get_motd repo = fetchFilePS (repo++"/"++darcsdir++"/prefs/motd") (MaxAge 600)+ `catchall` return B.empty++-- | Display the message of the day for a given repository,+-- unless either the 'XMLOutput' or the 'Quiet' flags are passed in+show_motd :: [DarcsFlag] -> String -> IO ()+show_motd opts repo = unless (Quiet `elem` opts || XMLOutput `elem` opts) $ do+ motd <- get_motd repo+ unless (B.null motd)+ $ do B.hPut stdout motd+ putStrLn "**********************"+\end{code}
@@ -0,0 +1,528 @@+% Copyright (C) 2002-2003 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.+++\begin{code}+{-# OPTIONS_GHC -cpp -fglasgow-exts #-}+{-# LANGUAGE CPP #-}++#include "gadts.h"++module Darcs.Repository.Prefs ( add_to_preflist, get_preflist, set_preflist,+ get_global, environmentHelpHome,+ defaultrepo, set_defaultrepo,+ get_prefval, set_prefval, change_prefval,+ def_prefval,+ write_default_prefs,+ boring_regexps, boring_file_filter, darcsdir_filter,+ FileType(..), filetype_function,+ getCaches,+ binaries_file_help+ ) where++import System.IO.Error ( isDoesNotExistError )+import Control.Monad ( unless, when, mplus )+import Text.Regex ( Regex, mkRegex, matchRegex, )+import Data.Char ( toUpper )+import Data.Maybe ( isJust, catMaybes )+import Data.List ( nub, isPrefixOf, union )+import System.Directory ( getAppUserDataDirectory )+import System.FilePath ( (</>) )++import Darcs.Flags ( DarcsFlag( NoCache, NoSetDefault, DryRun, Ephemeral, RemoteRepo ) )+import Darcs.RepoPath ( AbsolutePath, ioAbsolute, toFilePath, getCurrentDirectory )+import Darcs.Utils ( catchall, stripCr )+import Darcs.IO ( ReadableDirectory(..), WriteableDirectory(..) )+import Darcs.Patch.FileName ( fp2fn )+import Darcs.External ( gzFetchFilePS, Cachable( Cachable ) )+import qualified Data.ByteString.Char8 as BC ( unpack )+import qualified Data.ByteString as B ( empty )+import Darcs.Global ( darcsdir )+import Darcs.Repository.Cache ( Cache(..), CacheType(..), CacheLoc(..),+ WritableOrNot(..) )+import Darcs.URL ( is_file )+\end{code}++\section{prefs}++The \verb!_darcs! directory contains a \verb!prefs! directory. This+directory exists simply to hold user configuration settings specific to+this repository. The contents of this directory are intended to be+modifiable by the user, although in some cases a mistake in such a+modification may cause darcs to behave strangely.++++\input{Darcs/ArgumentDefaults.lhs}++\begin{code}+write_default_prefs :: IO ()+write_default_prefs = do set_preflist "boring" default_boring+ set_preflist "binaries" default_binaries+ set_preflist "motd" []+\end{code}++\paragraph{repos}+The \verb!_darcs/prefs/repos! file contains a list of repositories you have+pulled from or pushed to, and is used for autocompletion of pull and push+commands in bash. Feel free to delete any lines from this list that might+get in there, or to delete the file as a whole.++\paragraph{author}\label{author_prefs}+The \verb!_darcs/prefs/author! file contains the email address (or name) to+be used as the author when patches are recorded in this repository,+e.g.\ \verb!David Roundy <droundy@abridgegame.org>!. This+file overrides the contents of the environment variables+\verb!$DARCS_EMAIL! and \verb!$EMAIL!.++\paragraph{boring}\label{boring}+The \verb!_darcs/prefs/boring! file may contain a list of regular+expressions describing files, such as object files, that you do not expect+to add to your project. As an example, the boring file that I use with+my darcs repository is:+\begin{verbatim}+\.hi$+\.o$+^\.[^/]+^_+~$+(^|/)CVS($|/)+\end{verbatim}+A newly created repository has a longer boring file that+includes many common source control, backup, temporary, and compiled files.++You may want to have the boring file under version+control. To do this you can use darcs setpref to set the value+``boringfile'' to the name of your desired boring file+(e.g.\ \verb-darcs setpref boringfile .boring-, where \verb-.boring-+is the repository path of a file+that has been+darcs added to your repository). The boringfile preference overrides+\verb!_darcs/prefs/boring!, so be sure to copy that file to the boringfile.++You can also set up a ``boring'' regexps+file in your home directory, named \verb!~/.darcs/boring!, +on MS Windows~\ref{ms_win}, which will be+used with all of your darcs repositories.++Any file not already managed by darcs and whose repository path (such+as \verb!manual/index.html!) matches any of+the boring regular expressions is considered boring. The boring file is+used to filter the files provided to darcs add, to allow you to use a+simple \verb-darcs add newdir newdir/*-+without accidentally adding a bunch of+object files. It is also used when the \verb!--look-for-adds! flag is+given to whatsnew or record.+Note that once a file has been added to darcs, it is not considered+boring, even if it matches the boring file filter.++\begin{code}+{-# NOINLINE default_boring #-}+default_boring :: [String]+default_boring = ["# Boring file regexps:",+ "",+ "### compiler and interpreter intermediate files",+ "# haskell (ghc) interfaces",+ "\\.hi$", "\\.hi-boot$", "\\.o-boot$",+ "# object files",+ "\\.o$","\\.o\\.cmd$",+ "# profiling haskell",+ "\\.p_hi$", "\\.p_o$",+ "# haskell program coverage resp. profiling info",+ "\\.tix$", "\\.prof$",+ "# fortran module files",+ "\\.mod$",+ "# linux kernel",+ "\\.ko\\.cmd$","\\.mod\\.c$",+ "(^|/)\\.tmp_versions($|/)",+ "# *.ko files aren't boring by default because they might",+ "# be Korean translations rather than kernel modules",+ "# \\.ko$",+ "# python, emacs, java byte code",+ "\\.py[co]$", "\\.elc$","\\.class$",+ "# objects and libraries; lo and la are libtool things",+ "\\.(obj|a|exe|so|lo|la)$",+ "# compiled zsh configuration files",+ "\\.zwc$",+ "# Common LISP output files for CLISP and CMUCL",+ "\\.(fas|fasl|sparcf|x86f)$",+ "",+ "### build and packaging systems",+ "# cabal intermediates",+ "\\.installed-pkg-config",+ "\\.setup-config",+ "# standard cabal build dir, might not be boring for everybody",+ "# ^dist(/|$)",+ "# autotools",+ "(^|/)autom4te\\.cache($|/)", "(^|/)config\\.(log|status)$",+ "# microsoft web expression, visual studio metadata directories",+ "\\_vti_cnf$",+ "\\_vti_pvt$",+ "# gentoo tools",+ "\\.revdep-rebuild.*",+ "# generated dependencies",+ "^\\.depend$",+ "",+ "### version control systems",+ "# cvs",+ "(^|/)CVS($|/)","\\.cvsignore$",+ "# cvs, emacs locks",+ "^\\.#",+ "# rcs",+ "(^|/)RCS($|/)", ",v$",+ "# subversion",+ "(^|/)\\.svn($|/)",+ "# mercurial",+ "(^|/)\\.hg($|/)",+ "# git",+ "(^|/)\\.git($|/)",+ "# bzr",+ "\\.bzr$",+ "# sccs",+ "(^|/)SCCS($|/)",+ "# darcs",+ "(^|/)"++darcsdir++"($|/)", "(^|/)\\.darcsrepo($|/)",+ "^\\.darcs-temp-mail$",+ "-darcs-backup[[:digit:]]+$",+ "# gnu arch",+ "(^|/)(\\+|,)",+ "(^|/)vssver\\.scc$",+ "\\.swp$","(^|/)MT($|/)",+ "(^|/)\\{arch\\}($|/)","(^|/).arch-ids($|/)",+ "# bitkeeper",+ "(^|/)BitKeeper($|/)","(^|/)ChangeSet($|/)",+ "",+ "### miscellaneous",+ "# backup files",+ "~$","\\.bak$","\\.BAK$",+ "# patch originals and rejects",+ "\\.orig$", "\\.rej$",+ "# X server",+ "\\..serverauth.*",+ "# image spam",+ "\\#", "(^|/)Thumbs\\.db$",+ "# vi, emacs tags",+ "(^|/)(tags|TAGS)$",+ "#(^|/)\\.[^/]",+ "# core dumps",+ "(^|/|\\.)core$",+ "# partial broken files (KIO copy operations)",+ "\\.part$",+ "# waf files, see http://code.google.com/p/waf/",+ "(^|/)\\.waf-[[:digit:].]+-[[:digit:]]+($|/)",+ "(^|/)\\.lock-wscript$",+ "# mac os finder",+ "(^|/)\\.DS_Store$" ]++darcsdir_filter :: [FilePath] -> [FilePath]+darcsdir_filter = filter (not.is_darcsdir)+is_darcsdir :: FilePath -> Bool+is_darcsdir ('.':'/':f) = is_darcsdir f+is_darcsdir "." = True+is_darcsdir "" = True+is_darcsdir ".." = True+is_darcsdir "../" = True+is_darcsdir fp = darcsdir `isPrefixOf` fp++-- | The path of the global preference directory; @~/.darcs@ on Unix,+-- and @%APPDATA%/darcs@ on Windows.+global_prefs_dir :: IO (Maybe FilePath)+global_prefs_dir = (getAppUserDataDirectory "darcs" >>= return.Just)+ `catchall` (return Nothing)++environmentHelpHome :: ([String], [String])+environmentHelpHome = (["HOME", "APPDATA"], [+ "Per-user preferences are set in $HOME/.darcs (on Unix) or",+ "%APPDATA%/darcs (on Windows). This is also the default location of",+ "the cache."])++get_global :: String -> IO [String]+get_global f = do+ dir <- global_prefs_dir+ case dir of+ (Just d) -> get_preffile $ d </> f+ Nothing -> return []++global_cache_dir :: IO (Maybe FilePath)+global_cache_dir = slash_cache `fmap` global_prefs_dir+ where slash_cache = fmap (</> "cache")++boring_regexps :: IO [Regex]+boring_regexps = do+ borefile <- def_prefval "boringfile" (darcsdir ++ "/prefs/boring")+ bores <- get_lines borefile `catchall` return []+ gbs <- get_global "boring"+ return $ map mkRegex $ bores ++ gbs++boring_file_filter :: IO ([FilePath] -> [FilePath])+boring_file_filter = boring_regexps >>= return . actual_boring_file_filter++noncomments :: [String] -> [String]+noncomments ss = filter is_ok ss+ where is_ok "" = False+ is_ok ('#':_) = False+ is_ok _ = True++get_lines :: ReadableDirectory m => FilePath -> m [String]+get_lines f = (notconflicts . noncomments . map stripCr . lines)+ `fmap` mReadBinFile (fp2fn f)+ where notconflicts = filter nc+ startswith [] _ = True+ startswith (x:xs) (y:ys) | x == y = startswith xs ys+ startswith _ _ = False+ nc l | startswith "v v v v v v v" l = False+ nc l | startswith "*************" l = False+ nc l | startswith "^ ^ ^ ^ ^ ^ ^" l = False+ nc _ = True++-- | From a list of paths, filter out any that are within @_darcs@ or+-- match a boring regexp.+actual_boring_file_filter :: [Regex] -> [FilePath] -> [FilePath]+actual_boring_file_filter regexps files = filter (not . boring . normalize) files+ where boring file = is_darcsdir file ||+ any (\regexp -> isJust $ matchRegex regexp file) regexps++normalize :: FilePath -> FilePath+normalize ('.':'/':f) = normalize f+normalize f = normalize_helper $ reverse f+ where+ normalize_helper ('/':rf) = normalize_helper rf+ normalize_helper rf = reverse rf+\end{code}++\paragraph{binaries}+The \verb!_darcs/prefs/binaries! file may contain a list of regular+expressions describing files that should be treated as binary files rather+than text files. Darcs automatically treats files containing+\verb!^Z\! or \verb!'\0'! within the first 4096 bytes as being binary files.+You probably will want to have the binaries file under+version control. To do this you can use darcs setpref to set the value+``binariesfile'' to the name of your desired binaries file+(e.g.\ \verb'darcs setpref binariesfile ./.binaries', where+\verb'.binaries' is a file that has been+darcs added to your repository). As with the boring file, you can also set+up a \verb!~/.darcs/binaries! file if you like, on MS Windows~\ref{ms_win}.++\begin{code}+data FileType = BinaryFile | TextFile+ deriving (Eq)++{-# NOINLINE default_binaries #-}+-- | The lines that will be inserted into @_darcs/prefs/binaries@ when+-- @darcs init@ is run. Hence, a list of comments, blank lines and+-- regular expressions (ERE dialect).+--+-- Note that while this matches .gz and .GZ, it will not match .gZ,+-- i.e. it is not truly case insensitive.+default_binaries :: [String]+default_binaries = help +++ ["\\.(" ++ e ++ "|" ++ map toUpper e ++ ")$" | e <- extensions ]+ where extensions = ["a","bmp","bz2","doc","elc","exe","gif","gz","iso",+ "jar","jpe?g","mng","mpe?g","p[nbgp]m","pdf","png",+ "pyc","so","tar","tgz","tiff?","z","zip"]+ help = map ("# "++) binaries_file_help++binaries_file_help :: [String]+binaries_file_help =+ ["This file contains a list of extended regular expressions, one per",+ "line. A file path matching any of these expressions is assumed to",+ "contain binary data (not text). The entries in ~/.darcs/binaries (if",+ "it exists) supplement those in this file.",+ "",+ "Blank lines, and lines beginning with an octothorpe (#) are ignored.",+ "See regex(7) for a description of extended regular expressions."]++filetype_function :: IO (FilePath -> FileType)+filetype_function = do+ binsfile <- def_prefval "binariesfile" (darcsdir ++ "/prefs/binaries")+ bins <- get_lines binsfile `catch`+ (\e-> if isDoesNotExistError e then return [] else ioError e)+ gbs <- get_global "binaries"+ regexes <- return (map (\r -> mkRegex r) (bins ++ gbs))+ let isbin f = or $ map (\r -> isJust $ matchRegex r f) regexes+ ftf f = if isbin $ normalize f then BinaryFile else TextFile+ in+ return ftf++-- this avoids a circular dependency with Repository+prefsDirectory :: ReadableDirectory m => m String+prefsDirectory =+ do darcs <- mDoesDirectoryExist $ fp2fn darcsdir+ if darcs+ then return $ darcsdir ++ "/prefs/"+ else fail $ "Directory " ++ darcsdir ++ "/ does not exist!"++withPrefsDirectory :: ReadableDirectory m => (String -> m ()) -> m ()+withPrefsDirectory j = do prefs <- prefsDirectory `mplus` return "x"+ when (prefs /= "x") $ j prefs++add_to_preflist :: WriteableDirectory m => String -> String -> m ()+add_to_preflist p s = withPrefsDirectory $ \prefs -> do+ hasprefs <- mDoesDirectoryExist $ fp2fn prefs+ unless hasprefs $ mCreateDirectory $ fp2fn prefs+ pl <- get_preflist p+ mWriteBinFile (fp2fn $ prefs ++ p) $ unlines $ union [s] pl++get_preflist :: ReadableDirectory m => String -> m [String]+get_preflist p = do prefs <- prefsDirectory `mplus` return "x"+ if (prefs /= "x") then get_preffile $ prefs ++ p+ else return []++get_preffile :: ReadableDirectory m => FilePath -> m [String]+get_preffile f = do+ hasprefs <- mDoesFileExist (fp2fn f)+ if hasprefs+ then get_lines f+ else return []++set_preflist :: WriteableDirectory m => String -> [String] -> m ()+set_preflist p ls = withPrefsDirectory $ \prefs -> do+ haspref <- mDoesDirectoryExist $ fp2fn prefs+ if haspref+ then mWriteBinFile (fp2fn $ prefs ++ p) (unlines ls)+ else return ()++def_prefval :: String -> String -> IO String+def_prefval p d = do+ pv <- get_prefval p+ case pv of Nothing -> return d+ Just v -> return v++get_prefval :: ReadableDirectory m => String -> m (Maybe String)+get_prefval p =+ do pl <- get_preflist "prefs"+ case map snd $ filter ((==p).fst) $ map (break (==' ')) pl of+ [val] -> case words val of+ [] -> return Nothing+ _ -> return $ Just $ tail val+ _ -> return Nothing++set_prefval :: WriteableDirectory m => String -> String -> m ()+set_prefval p v = do pl <- get_preflist "prefs"+ set_preflist "prefs" $+ filter ((/=p).fst.(break (==' '))) pl ++ [p++" "++v]++change_prefval :: WriteableDirectory m => String -> String -> String -> m ()+change_prefval p f t =+ do pl <- get_preflist "prefs"+ ov <- get_prefval p+ newval <- case ov of+ Nothing -> return t+ Just old -> if old == f then return t else return old+ set_preflist "prefs" $+ filter ((/=p).fst.(break(==' '))) pl ++ [p++" "++newval]++defaultrepo :: [DarcsFlag] -> AbsolutePath -> [String] -> IO [String]+defaultrepo opts _ [] =+ do let fixR r | not (is_file r) = return r+ | otherwise = do absr <- ioAbsolute r+ return $ toFilePath absr+ case [r | RemoteRepo r <- opts] of+ [] -> do defrepo <- get_preflist "defaultrepo"+ case defrepo of+ [r] -> (:[]) `fmap` fixR r+ _ -> return []+ rs -> mapM fixR rs+defaultrepo _ _ r = return r++set_defaultrepo :: String -> [DarcsFlag] -> IO ()+set_defaultrepo r opts = do doit <- if (NoSetDefault `notElem` opts && DryRun `notElem` opts && r_is_not_tmp)+ then return True+ else do olddef <-+ get_preflist "defaultrepo"+ return (olddef == [])+ when doit+ (set_preflist "defaultrepo" [r])+ add_to_preflist "repos" r+ `catchall` return () -- we don't care if this fails!+ where+ r_is_not_tmp = not $ r `elem` [x | RemoteRepo x <- opts]+\end{code}++\paragraph{email}+The \verb!_darcs/prefs/email! file is used to provide the e-mail address for your+repository that others will use when they \verb!darcs send! a patch back to you.+The contents of the file should simply be an e-mail address.+++\paragraph{sources}+The \verb!_darcs/prefs/sources! file is used to indicate alternative+locations from which to download patches when using a ``hashed''+repository. This file contains lines such as:+\begin{verbatim}+cache:/home/droundy/.darcs/cache+readonly:/home/otheruser/.darcs/cache+repo:http://darcs.net+\end{verbatim}+This would indicate that darcs should first look in+\verb!/home/droundy/.darcs/cache! for patches that might be missing, and if+the patch isn't there, it should save a copy there for future use. In that+case, darcs will look in \verb!/home/otheruser/.darcs/cache! to see if that+user might have downloaded a copy, but won't try to save a copy there, of+course. Finally, it will look in \verb!http://darcs.net!. Note that the+\verb!sources! file can also exist in \verb!~/.darcs/!. Also note that the+sources mentioned in your \verb!sources! file will be tried \emph{before}+the repository you are pulling from. This can be useful in avoiding+downloading patches multiple times when you pull from a remote repository+to more than one local repository.++We strongly advise that you enable a global cache directory, which will+allow darcs to avoid re-downloading patches (for example, when doing a+second darcs get of the same repository), and also allows darcs to use hard+links to reduce disk usage. To do this, simply+\begin{verbatim}+mkdir -p $HOME/.darcs/cache+echo cache:$HOME/.darcs/cache > $HOME/.darcs/sources+\end{verbatim}+Note that the cache directory should reside on the same filesystem as your+repositories, so you may need to vary this. You can also use multiple+cache directories on different filesystems, if you have several filesystems+on which you use darcs.++On MS Windows~\ref{ms_win})++\begin{code}+getCaches :: [DarcsFlag] -> String -> IO Cache+getCaches opts repodir =+ do here <- parsehs `fmap` get_preffile (darcsdir ++ "/prefs/sources")+ there <- (parsehs . lines . BC.unpack) `fmap`+ (gzFetchFilePS (repodir ++ "/" ++ darcsdir ++ "/prefs/sources") Cachable+ `catchall` return B.empty)+ globalcachedir <- global_cache_dir+ let globalcache = case (nocache,globalcachedir) of+ (True,_) -> []+ (_,Just d) -> [Cache Directory Writable d]+ _ -> []+ globalsources <- parsehs `fmap` get_global "sources"+ thisdir <- getCurrentDirectory+ let thisrepo = if Ephemeral `elem` opts+ then [Cache Repo NotWritable $ toFilePath thisdir]+ else [Cache Repo Writable $ toFilePath thisdir]+ return $ Ca $ nub $ thisrepo ++ globalcache ++ globalsources +++ here ++ [Cache Repo NotWritable repodir] ++ there+ where + parsehs = catMaybes . map readln . noncomments+ readln l | take 5 l == "repo:" = Just (Cache Repo NotWritable (drop 5 l))+ | take 9 l == "thisrepo:" = Just (Cache Repo Writable (drop 9 l))+ | nocache = Nothing+ | take 6 l == "cache:" = Just (Cache Directory Writable (drop 6 l))+ | take 9 l == "readonly:" = Just (Cache Directory NotWritable (drop 9 l))+ | otherwise = Nothing+ nocache = NoCache `elem` opts+ +\end{code}+
@@ -0,0 +1,198 @@+-- Copyright (C) 2002-2005 David Roundy+-- Copyright (C) 2004 Juliusz Chroboczek+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.++{-# OPTIONS_GHC -cpp #-}+{-# LANGUAGE CPP #-}++#include "gadts.h"++module Darcs.Repository.Pristine ( Pristine, flagsToPristine, nopristine,+ createPristine, removePristine, identifyPristine,+ slurpPristine,+ applyPristine, createPristineFromWorking,+ syncPristine, replacePristineFromSlurpy,+ getPristinePop,+ pristineDirectory, pristineToFlagString,+ easyCreatePristineDirectoryTree,+ easyCreatePartialsPristineDirectoryTree+ ) where++import Data.Maybe ( isJust )+import Control.Monad ( when, liftM )+import System.Directory ( createDirectory, doesDirectoryExist, doesFileExist,+ renameDirectory, removeFile )+import Darcs.Lock ( rm_recursive, writeBinFile )+import Darcs.Diff ( sync )+import Workaround ( getCurrentDirectory )+import Darcs.SlurpDirectory ( Slurpy, mmap_slurp, co_slurp, writeSlurpy )+import Darcs.Utils ( catchall )++import Darcs.PopulationData ( Population, getPopFrom )+import Darcs.Flags ( DarcsFlag( PristinePlain, PristineNone ) )+import Darcs.Repository.Format ( RepoFormat, format_has,+ RepoProperty(HashedInventory) )+import Darcs.IO ( WriteableDirectory(mWithCurrentDirectory) )+import Darcs.Patch ( Patchy, apply )+import Darcs.Patch.Info ( PatchInfo )+import Darcs.Patch.FileName ( fp2fn )+import qualified Data.ByteString as B (empty)+import Darcs.RepoPath ( FilePathLike, toFilePath )+import SHA1 ( sha1PS )+import Darcs.External ( cloneTree, cloneTreeExcept, clonePartialsTree )+import Darcs.Repository.InternalTypes ( Pristine(..) )+import Darcs.Global ( darcsdir )+#include "impossible.h"++nopristine :: Pristine+nopristine = NoPristine "aack?"++pristineName :: String+pristineName = "pristine"++identifyPristine :: IO (Pristine)+identifyPristine = do mp <- reallyIdentifyPristine+ case mp of+ Nothing -> fail "Pristine tree doesn't exist."+ Just pristine -> return pristine++reallyIdentifyPristine :: IO (Maybe Pristine)+reallyIdentifyPristine = + do dir <- findpristine doesDirectoryExist ""+ none <- findpristine doesFileExist ".none"+ hashinv <- doesFileExist $ darcsdir++"/hashed_inventory"+ hashpris <- doesDirectoryExist hashedPristineDirectory+ case (dir, none, hashinv && hashpris) of+ (Nothing, Nothing, False) -> return Nothing+ (Just n, Nothing, False) ->+ return (Just (PlainPristine n))+ (Nothing, Just n, False) ->+ return (Just (NoPristine n))+ (Nothing, Nothing, True) ->+ return (Just HashedPristine)+ _ -> fail "Multiple pristine trees."+ where findpristine fn ext =+ do e1 <- fn n1+ e2 <- fn n2+ case (e1, e2) of+ (False, False) -> return Nothing+ (True, False) -> return (Just n1)+ (False, True) -> return (Just n2)+ (True, True) -> fail "Multiple pristine trees."+ where n1 = darcsdir++"/pristine" ++ ext+ n2 = darcsdir++"/current" ++ ext++flagsToPristine :: [DarcsFlag] -> RepoFormat -> Pristine+flagsToPristine _ rf | format_has HashedInventory rf = HashedPristine+flagsToPristine (PristineNone : _) _ = NoPristine (darcsdir++"/" ++ pristineName ++ ".none")+flagsToPristine (PristinePlain : _) _ = PlainPristine (darcsdir++"/" ++ pristineName)+flagsToPristine (_ : t) rf = flagsToPristine t rf+flagsToPristine [] rf = flagsToPristine [PristinePlain] rf++createPristine :: Pristine -> IO Pristine+createPristine p = + do oldpristine <- reallyIdentifyPristine+ when (isJust oldpristine) $ fail "Pristine tree already exists."+ case p of+ NoPristine n -> writeBinFile n "Do not delete this file.\n"+ PlainPristine n -> createDirectory n+ HashedPristine -> do createDirectory hashedPristineDirectory+ writeFile (hashedPristineDirectory++"/"++sha1PS B.empty) ""+ return p++hashedPristineDirectory :: String+hashedPristineDirectory = darcsdir++"/pristine.hashed"++removePristine :: Pristine -> IO ()+removePristine (NoPristine n) = removeFile n+removePristine (PlainPristine n) = rm_recursive n+removePristine HashedPristine = rm_recursive hashedPristineDirectory++slurpPristine :: Pristine -> IO (Maybe Slurpy)+slurpPristine (PlainPristine n) = do cwd <- getCurrentDirectory+ slurpy <- mmap_slurp (cwd ++ "/" ++ n)+ return (Just slurpy)+slurpPristine (NoPristine _) = return Nothing+slurpPristine HashedPristine =+ bug "HashedPristine is not implemented yet."++applyPristine :: Patchy p => Pristine -> p C(x y) -> IO ()+applyPristine (NoPristine _) _ = return ()+-- We don't need flags for now, since we don't care about+-- SetScriptsExecutable for the pristine cache.+applyPristine (PlainPristine n) p =+ mWithCurrentDirectory (fp2fn n) $ apply [] p+applyPristine HashedPristine _ =+ bug "3 HashedPristine is not implemented yet."++createPristineFromWorking :: Pristine -> IO ()+createPristineFromWorking (NoPristine _) = return ()+createPristineFromWorking (PlainPristine n) = cloneTreeExcept [darcsdir] "." n+createPristineFromWorking HashedPristine =+ bug "HashedPristine is not implemented yet."++syncPristine :: Pristine -> IO ()+syncPristine (NoPristine _) = return ()+syncPristine (PlainPristine n) =+ do ocur <- mmap_slurp n+ owork <- co_slurp ocur "."+ sync n ocur owork+syncPristine HashedPristine = return () -- FIXME this should be implemented!++replacePristineFromSlurpy :: Slurpy -> Pristine -> IO ()+replacePristineFromSlurpy _ (NoPristine _) = return ()+replacePristineFromSlurpy s (PlainPristine n) =+ do rm_recursive nold+ `catchall` return ()+ writeSlurpy s ntmp+ renameDirectory n nold+ renameDirectory ntmp n+ return ()+ where nold = darcsdir ++ "/" ++ pristineName ++ "-old"+ ntmp = darcsdir ++ "/" ++ pristineName ++ "-tmp"+replacePristineFromSlurpy _ HashedPristine =+ bug "HashedPristine is not implemented yet."++getPristinePop :: PatchInfo -> Pristine -> IO (Maybe Population)+getPristinePop pinfo (PlainPristine n) =+ Just `liftM` getPopFrom n pinfo+getPristinePop _ _ = return Nothing++pristineDirectory :: Pristine -> Maybe String+pristineDirectory (PlainPristine n) = Just n+pristineDirectory _ = Nothing++pristineToFlagString :: Pristine -> String+pristineToFlagString (NoPristine _) = "--no-pristine-tree"+pristineToFlagString (PlainPristine _) = "--plain-pristine-tree"+pristineToFlagString HashedPristine =+ bug "HashedPristine is not implemented yet."++easyCreatePristineDirectoryTree :: Pristine -> FilePath -> IO Bool+easyCreatePristineDirectoryTree (NoPristine _) _ = return False+easyCreatePristineDirectoryTree (PlainPristine n) p+ = cloneTree n p >> return True+easyCreatePristineDirectoryTree HashedPristine _ =+ bug "HashedPristine is not implemented yet."++easyCreatePartialsPristineDirectoryTree :: FilePathLike fp => [fp] -> Pristine+ -> FilePath -> IO Bool+easyCreatePartialsPristineDirectoryTree _ (NoPristine _) _ = return False+easyCreatePartialsPristineDirectoryTree prefs (PlainPristine n) p+ = clonePartialsTree n p (map toFilePath prefs) >> return True+easyCreatePartialsPristineDirectoryTree _ HashedPristine _ =+ bug "HashedPristine is not implemented yet."
@@ -0,0 +1,165 @@+{-# OPTIONS_GHC -cpp #-}+{-# LANGUAGE CPP #-}++module Darcs.Repository.Repair ( replayRepository,+ RepositoryConsistency(..) )+ where+ +import Control.Monad ( when, unless )+import Control.Exception ( finally )+import Data.Maybe ( catMaybes )+import Data.List ( sort )+import System.Directory ( createDirectoryIfMissing )++import Darcs.SlurpDirectory ( empty_slurpy, withSlurpy, Slurpy, SlurpMonad, syncSlurpy )+import Darcs.Lock( rm_recursive )+import Darcs.Hopefully ( PatchInfoAnd, info )++import Darcs.Ordered ( FL(..), RL(..), lengthFL, reverseFL, reverseRL, concatRL,+ mapRL )+import Darcs.Patch.Depends ( get_patches_beyond_tag )+import Darcs.Patch.Patchy ( applyAndTryToFix )+import Darcs.Patch.Info ( PatchInfo( .. ), human_friendly )+import Darcs.Patch.Set ( PatchSet )+import Darcs.Patch ( RepoPatch, patch2patchinfo )++import Darcs.Repository.Format ( identifyRepoFormat, + RepoProperty ( HashedInventory ), format_has )+import Darcs.Repository.Cache ( Cache, HashedDir( HashedPristineDir ) )+import Darcs.Repository.HashedIO ( slurpHashedPristine, writeHashedPristine,+ clean_hashdir )+import Darcs.Repository.HashedRepo ( readHashedPristineRoot )+import Darcs.Repository.Checkpoint ( get_checkpoint_by_default )+import Darcs.Repository.InternalTypes ( extractCache )+import Darcs.Repository ( Repository, read_repo,+ checkPristineAgainstSlurpy,+ makePatchLazy )++import Darcs.Sealed ( Sealed(..), unsafeUnflippedseal )+import Progress ( debugMessage, beginTedious, endTedious, tediousSize, finishedOneIO )+import Darcs.Utils ( catchall )+import Darcs.Global ( darcsdir )+import Darcs.Flags ( compression )+import Printer ( Doc, putDocLn, text )+import Darcs.Arguments ( DarcsFlag( Verbose, Quiet ) )+#include "impossible.h"++run_slurpy :: Slurpy -> SlurpMonad a -> IO (Slurpy, a)+run_slurpy s f =+ case withSlurpy s f of+ Left err -> fail err+ Right x -> return x++update_slurpy :: Repository p -> Cache -> [DarcsFlag] -> Slurpy -> IO Slurpy+update_slurpy r c opts s = do+ current <- readHashedPristineRoot r+ h <- writeHashedPristine c (compression opts) s+ s' <- slurpHashedPristine c (compression opts) h+ clean_hashdir c HashedPristineDir $ catMaybes [Just h, current]+ return s'++replaceInFL :: FL (PatchInfoAnd a)+ -> [(PatchInfo, PatchInfoAnd a)]+ -> FL (PatchInfoAnd a)+replaceInFL orig [] = orig+replaceInFL NilFL _ = impossible+replaceInFL (o:>:orig) ch@((o',c):ch_rest)+ | info o == o' = c:>:replaceInFL orig ch_rest+ | otherwise = o:>:replaceInFL orig ch++applyAndFix :: RepoPatch p => Cache -> [DarcsFlag] -> Slurpy -> Repository p -> FL (PatchInfoAnd p) -> IO (FL (PatchInfoAnd p), Slurpy, Bool)+applyAndFix _ _ s _ NilFL = return (NilFL, s, True)+applyAndFix c opts s_ r psin =+ do beginTedious k+ tediousSize k $ lengthFL psin+ (repaired, slurpy, ok) <- aaf s_ psin+ endTedious k+ orig <- (reverseRL . concatRL) `fmap` read_repo r+ return (replaceInFL orig repaired, slurpy, ok)+ where k = "Replaying patch"+ aaf s NilFL = return ([], s, True)+ aaf s (p:>:ps) = do+ (s', mp') <- run_slurpy s $ applyAndTryToFix p+ let !infp = info p -- assure that 'p' can be garbage collected.+ finishedOneIO k $ show $ human_friendly $ infp+ s'' <- syncSlurpy (update_slurpy r c opts) s'+ (ps', s''', restok) <- aaf s'' ps+ case mp' of+ Nothing -> return (ps', s''', restok)+ Just (e,pp) -> do putStrLn e+ p' <- makePatchLazy r pp+ return ((infp, p'):ps', s''', False)++data RepositoryConsistency p =+ RepositoryConsistent+ | BrokenPristine Slurpy+ | BrokenPatches Slurpy (PatchSet p)++check_uniqueness :: RepoPatch p => (Doc -> IO ()) -> (Doc -> IO ()) -> Repository p -> IO ()+check_uniqueness putVerbose putInfo repository =+ do putVerbose $ text "Checking that patch names are unique..."+ r <- read_repo repository+ case has_duplicate $ mapRL info $ concatRL r of+ Nothing -> return ()+ Just pinf -> do putInfo $ text "Error! Duplicate patch name:"+ putInfo $ human_friendly pinf+ fail "Duplicate patches found."++has_duplicate :: Ord a => [a] -> Maybe a+has_duplicate li = hd $ sort li+ where hd [_] = Nothing+ hd [] = Nothing+ hd (x1:x2:xs) | x1 == x2 = Just x1+ | otherwise = hd (x2:xs)+replayRepository' :: (RepoPatch p) => Repository p -> [DarcsFlag] -> IO (RepositoryConsistency p)+replayRepository' repo opts = do+ let putVerbose s = when (Verbose `elem` opts) $ putDocLn s+ putInfo s = when (not $ Quiet `elem` opts) $ putDocLn s+ check_uniqueness putVerbose putInfo repo+ maybe_chk <- get_checkpoint_by_default repo+ let c = extractCache repo+ createDirectoryIfMissing False $ darcsdir ++ "/pristine.hashed"+ rooth <- writeHashedPristine c (compression opts) empty_slurpy+ s <- slurpHashedPristine c (compression opts) rooth+ putVerbose $ text "Applying patches..."+ patches <- read_repo repo+ (s', newpatches, patches_ok) <- case maybe_chk of+ Just (Sealed chk) ->+ do let chtg = patch2patchinfo chk+ putVerbose $ text "I am repairing from a checkpoint."+ (s'', _) <- run_slurpy s $ applyAndTryToFix chk+ (_, s_, ok) <- applyAndFix c opts s'' repo+ (reverseRL $ concatRL $ unsafeUnflippedseal $ get_patches_beyond_tag chtg patches)+ return (s_, patches, ok)+ Nothing -> do debugMessage "Fixing any broken patches..."+ let psin = reverseRL $ concatRL patches+ (ps, s_, ok) <- applyAndFix c opts s repo psin+ debugMessage "Done fixing broken patches..."+ return (s_, (reverseFL ps :<: NilRL), ok)+ debugMessage "Checking pristine against slurpy"+ is_same <- checkPristineAgainstSlurpy repo s' `catchall` return False+ -- TODO is the latter condition needed? Does a broken patch imply pristine+ -- difference? Why, or why not?+ return (if is_same && patches_ok+ then RepositoryConsistent+ else if patches_ok+ then BrokenPristine s'+ else BrokenPatches s' newpatches)++cleanupRepositoryReplay :: Repository p -> IO ()+cleanupRepositoryReplay r = do+ let c = extractCache r+ rf_or_e <- identifyRepoFormat "."+ rf <- case rf_or_e of Left e -> fail e+ Right x -> return x+ unless (format_has HashedInventory rf) $+ rm_recursive $ darcsdir ++ "/pristine.hashed" + when (format_has HashedInventory rf) $ do+ current <- readHashedPristineRoot r+ clean_hashdir c HashedPristineDir $ catMaybes [current]++replayRepository :: (RepoPatch p) => Repository p -> [DarcsFlag] -> (RepositoryConsistency p -> IO a) -> IO a+replayRepository r opt f = run `finally` cleanupRepositoryReplay r+ where run = do+ st <- replayRepository' r opt+ f st
@@ -0,0 +1,240 @@+% Copyright (C) 2003,2005 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\begin{code}+{-# OPTIONS_GHC -cpp #-}+{-# LANGUAGE CPP #-}++#include "gadts.h"++module Darcs.Resolution ( standard_resolution,+ external_resolution,+ patchset_conflict_resolutions,+ ) where++import System.FilePath.Posix ( (</>) )+import System.Exit ( ExitCode( ExitSuccess ) )+import System.Directory ( setCurrentDirectory, getCurrentDirectory )+import Data.List ( zip4 )+import Control.Monad ( when )++import Darcs.Patch ( RepoPatch, Prim, joinPatches, resolve_conflicts,+ effect,+ apply_to_filepaths, patchcontents,+ invert, list_conflicted_files, commute )+import Darcs.RepoPath ( toFilePath )+import Darcs.Ordered ( FL(..), RL(..), (:>)(..), (+>+),+ mapFL_FL, reverseRL, lengthFL )++import CommandLine ( parseCmd )+import Darcs.Hopefully ( hopefully )+import Darcs.Utils ( askUser )+import Darcs.SlurpDirectory ( Slurpy, slurp, write_files )+import Darcs.Patch.Set ( PatchSet )+import Darcs.Diff ( unsafeDiff )+import Darcs.Sealed ( Sealed(..) )+import Darcs.Repository.Prefs ( filetype_function )+import Exec ( exec, Redirect(..) )+import Darcs.Lock ( withTempDir )+import Darcs.External ( cloneTree )+import Darcs.Patch.Apply ( apply_to_slurpy )++--import Darcs.ColorPrinter ( traceDoc )+--import Printer ( greenText, ($$), Doc )+--import Darcs.Patch ( showPatch )++standard_resolution :: RepoPatch p => p C(x y) -> Sealed (FL Prim C(y))+standard_resolution p = merge_list $ map head $ resolve_conflicts p++merge_list :: [Sealed (FL Prim C(x))] -> Sealed (FL Prim C(x))+merge_list patches = doml NilFL patches+ where doml :: FL Prim C(x y) -> [Sealed (FL Prim C(x))] -> Sealed (FL Prim C(x))+ doml mp (Sealed p:ps) =+ case commute (invert p :> mp) of+ Just (mp' :> _) -> doml (effect p +>+ effect mp') ps+ Nothing -> doml mp ps -- This shouldn't happen for "good" resolutions.+ doml mp [] = Sealed mp+\end{code}++\paragraph{Resolution of conflicts}\label{resolution}++To resolve conflicts using an external tool, you need to specify a command+to use, e.g.+\begin{verbatim}+--external-merge 'opendiff %1 %2 -ancestor %a -merge %o'+\end{verbatim}+The \verb!%1! and \verb!%2! are replaced with the two versions to be+merged, \verb!%a! is replaced with the common ancestor of the two versions.+Most importantly, \verb!%o! is replaced with the name of the output file+that darcs will require to be created holding the merged version. The+above example works with the FileMerge.app tool that comes with Apple's+developer tools. To use xxdiff, you would use+\begin{verbatim}+--external-merge 'xxdiff -m -O -M %o %1 %a %2'+\end{verbatim}+To use \verb!kdiff3!, you can use+\begin{verbatim}+--external-merge 'kdiff3 --output %o %a %1 %2'+\end{verbatim}+To use \verb!tortoiseMerge!, you can use+\begin{verbatim}+--external-merge 'tortoiseMerge /base:"%a" /mine:"%1" /theirs:"%2" /merged:"%o"'+\end{verbatim}+(\verb!tortoiseMerge! is a nice merge tool that comes with TortoiseSVN and works well+on Windows.)++% Fixme: Is it actually a shell command on MS Windows?+Note that the command is split into space-separated words and the first one is+\verb!exec!ed with the rest as arguments---it is not a shell command. In particular,+on Windows this means that the first command path should not contain spaces and+you should make sure the command is in your \verb!PATH!. ++The substitution of the \verb!%! escapes is done everywhere. If you need to prevent+substitution you can use a double percentage sign, i.e. \verb!%%a! is substituted with+\verb!%a!. Here is an example script to use the Emacs' Ediff package for merging.+% This is indented so that the leading #s don't confuse the preprocessor.+\begin{verbatim}+ #! /bin/sh+ # External merge command for darcs, using Emacs Ediff, via server if possible.+ # It needs args %1 %2 %a %o, i.e. the external merge command is, say,+ # `emerge3 %1 %2 %a %o'.+ test $# -eq 4 || exit 1+ form="(ediff-merge-files-with-ancestor"+ while test $# -gt 0; do+ count=$count.+ if [ $count = .... ]; then+ form=$form\ nil # Lisp STARTUP-HOOKS arg+ fi+ case $1 in # Worry about quoting -- escape " and \+ *[\"\\]* ) form=$form\ \"$(echo $1 | sed -e's/["\\]/\\\0/g')\" ;;+ *) form=$form\ \"$1\" ;;+ esac+ shift+ done+ form=$form')'+ ( emacsclient --eval "$form" || # Emacs 22 server+ gnudoit "$form" || # XEmacs/Emacs 21 server+ emacs --eval "$form" || # Relatively slow to start up+ xemacs -eval "$form" # Horribly slow to start up+ ) 2>/dev/null+\end{verbatim}+It would be invoked like:+\begin{verbatim}+--external-merge 'emerge3 %1 %2 %a %o'+\end{verbatim}++If you figure out how to use darcs with another merge tool, please let me+know what flags you used so I can mention it here.++Note that if you do use an external merge tool, most likely you will want+to add to your defaults file+(\verb!_darcs/prefs/defaults! or \verb!~/.darcs/prefs!, see \ref{defaults}, +on MS Windows~\ref{ms_win})+a line such as+\begin{verbatim}+ALL external-merge kdiff3 --output %o %a %1 %2+\end{verbatim}+or+\begin{verbatim}+ALL external-merge tortoiseMerge /base:"%a" /mine:"%1" /theirs:"%2" /merged:"%o"+\end{verbatim}++Note that the defaults file does not want quotes around the command.++\begin{code}+external_resolution :: RepoPatch p => Slurpy -> String -> FL Prim C(x y) -> FL Prim C(x z)+ -> p C(y a)+ -> IO (Sealed (FL Prim C(a)))+external_resolution s1 c p1 p2 pmerged = do+ sa <- apply_to_slurpy (invert p1) s1+ sm <- apply_to_slurpy pmerged s1+ s2 <- apply_to_slurpy p2 sa+ let nms = list_conflicted_files pmerged+ nas = apply_to_filepaths (invert pmerged) nms+ n1s = apply_to_filepaths p1 nas+ n2s = apply_to_filepaths p2 nas+ ns = zip4 nas n1s n2s nms+ in do+ former_dir <- getCurrentDirectory+ withTempDir "version1" $ \absd1 -> do+ let d1 = toFilePath absd1+ write_files s1 n1s+ setCurrentDirectory former_dir+ withTempDir "ancestor" $ \absda -> do+ let da = toFilePath absda+ write_files sa nas+ setCurrentDirectory former_dir+ withTempDir "merged" $ \absdm -> do+ let dm = toFilePath absdm+ write_files sm nms+ setCurrentDirectory former_dir+ withTempDir "cleanmerged" $ \absdc -> do+ let dc = toFilePath absdc+ cloneTree dm "."+ setCurrentDirectory former_dir+ withTempDir "version2" $ \absd2 -> do+ let d2 = toFilePath absd2+ write_files s2 n2s+ mapM_ (externally_resolve_file c da d1 d2 dm) ns+ sc <- slurp dc+ sfixed <- slurp dm+ ftf <- filetype_function+ case unsafeDiff [] ftf sc sfixed of+ di -> lengthFL di `seq` return (Sealed di)+ -- The `seq` above forces the two slurpies to be read before+ -- we delete their directories.++externally_resolve_file :: String -> String -> String -> String -> String+ -> (FilePath, FilePath, FilePath, FilePath)+ -> IO ()+externally_resolve_file c da d1 d2 dm (fa, f1, f2, fm) = do+ putStrLn $ "Merging file "++fm++" by hand."+ ec <- run c [('1', d1</>f1), ('2', d2</>f2), ('a', da</>fa), ('o', dm</>fm), ('%', "%")]+ when (ec /= ExitSuccess) $+ putStrLn $ "External merge command exited with " ++ show ec+ askUser "Hit return to move on, ^C to abort the whole operation..."+ return ()++run :: String -> [(Char,String)] -> IO ExitCode+run c replacements =+ case parseCmd replacements c of+ Left err -> fail $ show err+ Right (c2,_) -> rr c2+ where rr (command:args) = do putStrLn $ "Running command '" +++ unwords (command:args) ++ "'"+ exec command args (Null,Null,Null)+ rr [] = return ExitSuccess++patchset_conflict_resolutions :: RepoPatch p => PatchSet p C(x) -> Sealed (FL Prim C(x))+patchset_conflict_resolutions (NilRL:<:_) = --traceDoc (greenText "no conflicts A") $+ Sealed NilFL+patchset_conflict_resolutions NilRL = --traceDoc (greenText "no conflicts B") $+ Sealed NilFL+patchset_conflict_resolutions (xs:<:_)+ = --traceDoc (greenText "looking at resolutions" $$+ -- (sh $ resolve_conflicts $ joinPatches $+ -- mapFL_FL (patchcontents . hopefully) $ reverseRL xs )) $+ merge_list $ map head $ resolve_conflicts $ joinPatches $+ mapFL_FL (patchcontents . hopefully) $ reverseRL xs+ --where sh :: [[Sealed (FL Prim)]] -> Doc+ -- sh [] = greenText "no more conflicts"+ -- sh (x:ps) = greenText "one conflict" $$ sh1 x $$ sh ps+ -- sh1 :: [Sealed (FL Prim)] -> Doc+ -- sh1 [] = greenText "end of unravellings"+ -- sh1 (Sealed x:ps) = greenText "one unravelling:" $$ showPatch x $$+ -- sh1 ps+\end{code}
@@ -0,0 +1,185 @@+-- Copyright (C) 2002,2003,2005 David Roundy+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.++module Darcs.RunCommand ( run_the_command ) where++import Control.Monad ( unless, when )+import System.Console.GetOpt( ArgOrder( Permute, RequireOrder ),+ OptDescr( Option ),+ getOpt )+import System.Exit ( ExitCode ( ExitSuccess ), exitWith )++import Darcs.Arguments ( DarcsFlag(..),+ help,+ option_from_darcsoption,+ list_options )+import Darcs.ArgumentDefaults ( get_default_flags )+import Darcs.Commands ( CommandArgs( CommandOnly, SuperCommandOnly, SuperCommandSub ),+ DarcsCommand,+ command_name,+ command_command,+ command_prereq,+ command_extra_arg_help,+ command_extra_args,+ command_argdefaults,+ command_get_arg_possibilities,+ command_options, command_alloptions,+ disambiguate_commands,+ get_command_help, get_command_mini_help,+ get_subcommands,+ extract_commands,+ super_name,+ subusage, chomp_newline )+#ifdef HAVE_HASKELL_ZLIB+import Darcs.Commands.GZCRCs ( doCRCWarnings )+import Darcs.Global ( atexit )+#endif+import Darcs.Commands.Help ( command_control_list )+import Darcs.External ( viewDoc )+import Darcs.Global ( setDebugMode, setSshControlMasterDisabled,+ setTimingsMode, setVerboseMode )+import Darcs.Match ( checkMatchSyntax )+import Progress ( setProgressMode )+import Darcs.RepoPath ( getCurrentDirectory )+import Darcs.Test ( run_posthook, run_prehook )+import Darcs.Utils ( formatPath )+import Printer ( text )+import URL ( setDebugHTTP, setHTTPPipelining )++run_the_command :: String -> [String] -> IO ()+run_the_command cmd args =+ either fail rtc $ disambiguate_commands command_control_list cmd args+ where+ rtc (CommandOnly c, as) = run_command Nothing c as+ rtc (SuperCommandOnly c, as) = run_raw_supercommand c as+ rtc (SuperCommandSub c s, as) = run_command (Just c) s as++-- This is the actual heavy lifter code, which is responsible for parsing the+-- arguments and then running the command itself.++run_command :: Maybe DarcsCommand -> DarcsCommand -> [String] -> IO ()++run_command _ _ args -- Check for "dangerous" typoes...+ | "-all" `elem` args = -- -all indicates --all --look-for-adds!+ fail $ "Are you sure you didn't mean -" ++ "-all rather than -all?"+run_command msuper cmd args = do+ cwd <- getCurrentDirectory+ let options = opts1 ++ opts2+ (opts1, opts2) = command_options cwd cmd+ case getOpt Permute+ (option_from_darcsoption cwd list_options++options) args of+ (opts,extra,[])+ | Help `elem` opts -> viewDoc $ text $ get_command_help msuper cmd+ | ListOptions `elem` opts -> do+ setProgressMode False+ command_prereq cmd opts+ file_args <- command_get_arg_possibilities cmd+ putStrLn $ get_options_options (opts1++opts2) ++ unlines file_args+ | otherwise -> consider_running msuper cmd (addVerboseIfDebug opts) extra+ (_,_,ermsgs) -> do fail $ chomp_newline(unlines ermsgs)+ where addVerboseIfDebug opts | DebugVerbose `elem` opts = Debug:Verbose:opts+ | otherwise = opts++consider_running :: Maybe DarcsCommand -> DarcsCommand+ -> [DarcsFlag] -> [String] -> IO ()+consider_running msuper cmd opts old_extra = do+ cwd <- getCurrentDirectory+ location <- command_prereq cmd opts+ case location of+ Left complaint -> fail $ "Unable to " +++ formatPath ("darcs " ++ super_name msuper ++ command_name cmd) +++ " here.\n\n" ++ complaint+ Right () -> do+ specops <- add_command_defaults cmd opts+ extra <- (command_argdefaults cmd) specops cwd old_extra+ when (Disable `elem` specops) $+ fail $ "Command "++command_name cmd++" disabled with --disable option!"+ if command_extra_args cmd < 0+ then runWithHooks specops extra+ else if length extra > command_extra_args cmd+ then fail $ "Bad argument: `"++unwords extra++"'\n"+++ get_command_mini_help msuper cmd+ else if length extra < command_extra_args cmd+ then fail $ "Missing argument: " +++ nth_arg (length extra + 1) +++ "\n" ++ get_command_mini_help msuper cmd+ else runWithHooks specops extra+ where nth_arg n = nth_of n (command_extra_arg_help cmd)+ nth_of 1 (h:_) = h+ nth_of n (_:hs) = nth_of (n-1) hs+ nth_of _ [] = "UNDOCUMENTED"+ runWithHooks os ex = do+ here <- getCurrentDirectory+ checkMatchSyntax os+ -- set any global variables+ when (Timings `elem` os) setTimingsMode+ when (Debug `elem` os) setDebugMode+ when (DebugHTTP `elem` os) setDebugHTTP+ when (Verbose `elem` os) setVerboseMode+ when (Quiet `elem` os) $ setProgressMode False+ when (HTTPPipelining `elem` os) $ setHTTPPipelining True+ when (NoHTTPPipelining `elem` os) $ setHTTPPipelining False+ unless (SSHControlMaster `elem` os) setSshControlMasterDisabled+#ifdef HAVE_HASKELL_ZLIB+ unless (Quiet `elem` os) $ atexit $ doCRCWarnings (Verbose `elem` os)+#endif+ -- actually run the command and its hooks+ preHookExitCode <- run_prehook os here+ if preHookExitCode /= ExitSuccess+ then exitWith preHookExitCode+ else do let fixFlag = FixFilePath here cwd+ (command_command cmd) (fixFlag : os) ex+ postHookExitCode <- run_posthook os here+ exitWith postHookExitCode++add_command_defaults :: DarcsCommand -> [DarcsFlag] -> IO [DarcsFlag]+add_command_defaults cmd already = do+ let (opts1, opts2) = command_alloptions cmd+ defaults <- get_default_flags (command_name cmd) (opts1 ++ opts2) already+ return $ already ++ defaults++get_options_options :: [OptDescr DarcsFlag] -> String+get_options_options [] = ""+get_options_options (o:os) =+ get_long_option o ++"\n"++ get_options_options os++get_long_option :: OptDescr DarcsFlag -> String+get_long_option (Option _ [] _ _) = ""+get_long_option (Option a (o:os) b c) = "--"++o+++ get_long_option (Option a os b c)++run_raw_supercommand :: DarcsCommand -> [String] -> IO ()+run_raw_supercommand super [] =+ fail $ "Command '"++ command_name super ++"' requires subcommand!\n\n"+ ++ subusage super+run_raw_supercommand super args = do+ cwd <- getCurrentDirectory+ case getOpt RequireOrder+ (option_from_darcsoption cwd help+++ option_from_darcsoption cwd list_options) args of+ (opts,_,[])+ | Help `elem` opts ->+ viewDoc $ text $ get_command_help Nothing super+ | ListOptions `elem` opts -> do+ putStrLn "--help"+ mapM_ (putStrLn . command_name) (extract_commands $ get_subcommands super)+ | otherwise ->+ if Disable `elem` opts+ then fail $ "Command " ++ (command_name super) +++ " disabled with --disable option!"+ else fail $ "Invalid subcommand!\n\n" ++ subusage super+ (_,_,ermsgs) -> do fail $ chomp_newline(unlines ermsgs)
@@ -0,0 +1,103 @@+-- Copyright (C) 2007 David Roundy+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.++{-# OPTIONS_GHC -cpp -fglasgow-exts #-}+{-# LANGUAGE CPP #-}+-- , MagicHash, GADTs #-}++#include "gadts.h"++module Darcs.Sealed ( Sealed(..), seal, unseal, mapSeal,+#ifndef GADT_WITNESSES+ unsafeUnseal, unsafeUnflippedseal, unsafeUnseal2,+#endif+ Sealed2(..), seal2, unseal2, mapSeal2,+ FlippedSeal(..), flipSeal, unsealFlipped, mapFlipped,+ unsealM, liftSM+ ) where++import GHC.Base ( unsafeCoerce# )+import Darcs.Show++data Sealed a where+ Sealed :: a C(x ) -> Sealed a++seal :: a C(x ) -> Sealed a+seal = Sealed++data Sealed2 a where+ Sealed2 :: !(a C(x y )) -> Sealed2 a++seal2 :: a C(x y ) -> Sealed2 a+seal2 = Sealed2++data FlippedSeal a C(y) where+ FlippedSeal :: !(a C(x y)) -> FlippedSeal a C(y)++flipSeal :: a C(x y) -> FlippedSeal a C(y)+flipSeal = FlippedSeal++#ifndef GADT_WITNESSES+unsafeUnseal :: Sealed a -> a+unsafeUnseal (Sealed a) = a++unsafeUnflippedseal :: FlippedSeal a -> a+unsafeUnflippedseal (FlippedSeal a) = a++unsafeUnseal2 :: Sealed2 a -> a+unsafeUnseal2 (Sealed2 a) = a+#endif++seriouslyUnsafeUnseal :: Sealed a -> a C(())+seriouslyUnsafeUnseal (Sealed a) = unsafeCoerce# a++unseal :: (FORALL(x) a C(x ) -> b) -> Sealed a -> b+unseal f x = f (seriouslyUnsafeUnseal x)++-- laziness property:+-- unseal (const True) undefined == True++unsealM :: Monad m => m (Sealed a) -> (FORALL(x) a C(x) -> m b) -> m b+unsealM m1 m2 = do sx <- m1+ unseal m2 sx++liftSM :: Monad m => (FORALL(x) a C(x) -> b) -> m (Sealed a) -> m b+liftSM f m = do sx <- m+ return (unseal f sx)++mapSeal :: (FORALL(x) a C(x ) -> b C(x )) -> Sealed a -> Sealed b+mapSeal f = unseal (seal . f)++mapFlipped :: (FORALL(x) a C(x y) -> b C(x z)) -> FlippedSeal a C(y) -> FlippedSeal b C(z)+mapFlipped f (FlippedSeal x) = FlippedSeal (f x)++seriouslyUnsafeUnseal2 :: Sealed2 a -> a C(() ())+seriouslyUnsafeUnseal2 (Sealed2 a) = unsafeCoerce# a++unseal2 :: (FORALL(x y) a C(x y ) -> b) -> Sealed2 a -> b+unseal2 f a = f (seriouslyUnsafeUnseal2 a)++mapSeal2 :: (FORALL(x y) a C(x y ) -> b C(x y )) -> Sealed2 a -> Sealed2 b+mapSeal2 f = unseal2 (seal2 . f)++unsealFlipped :: (FORALL(x y) a C(x y) -> b) -> FlippedSeal a C(z) -> b+unsealFlipped f (FlippedSeal a) = f a++instance Show1 a => Show (Sealed a) where+ showsPrec d (Sealed x) = showParen (d > app_prec) $ showString "Sealed " . showsPrec1 (app_prec + 1) x+instance Show2 a => Show (Sealed2 a) where+ showsPrec d (Sealed2 x) = showParen (d > app_prec) $ showString "Sealed2 " . showsPrec2 (app_prec + 1) x
@@ -0,0 +1,620 @@+-- Copyright (C) 2002-2003 David Roundy+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.++{-# OPTIONS_GHC -fglasgow-exts #-}+{-# LANGUAGE CPP, ForeignFunctionInterface #-}+-- , ScopedTypeVariables, TypeOperators, PatternGuards #-}++#include "gadts.h"++module Darcs.SelectChanges ( with_selected_changes',+ with_selected_changes_to_files',+ with_selected_last_changes_to_files',+ with_selected_last_changes_reversed',+ with_selected_changes,+ with_selected_changes_to_files,+ with_selected_changes_reversed,+ with_selected_last_changes_to_files,+ with_selected_last_changes_to_files_reversed,+ with_selected_last_changes_reversed,+ view_changes,+ with_selected_patch_from_repo,+ ) where+import System.IO+import Data.List ( intersperse )+import Data.Maybe ( catMaybes, isJust )+import Data.Char ( toUpper )+import Control.Monad ( when )+import System.Exit ( exitWith, ExitCode(ExitSuccess) )++import English ( Noun(..), englishNum )+import Darcs.Arguments ( showFriendly )+import Darcs.Hopefully ( PatchInfoAnd, hopefully )+import Darcs.Repository ( Repository, read_repo )+import Darcs.Patch ( RepoPatch, Patchy, Prim, summary,+ invert, list_touched_files,+ commuteFL )+import qualified Darcs.Patch ( thing, things )+import Darcs.Ordered ( FL(..), RL(..), (:>)(..),+ (+>+), lengthFL, lengthRL, concatRL, mapFL_FL,+ spanFL, reverseFL, (+<+), mapFL,+ unsafeCoerceP )+import Darcs.Patch.Choices ( PatchChoices, patch_choices, patch_choices_tps,+ force_first, force_last, make_uncertain, tag,+ get_choices,+ separate_first_middle_from_last,+ separate_first_from_middle_last,+ patch_slot,+ select_all_middles,+ force_matching_last,+ force_matching_first, make_everything_later,+ TaggedPatch, tp_patch, Slot(..),+ )+import Darcs.Patch.TouchesFiles ( deselect_not_touching, select_not_touching )+import Darcs.PrintPatch ( printFriendly, printPatch, printPatchPager )+import Darcs.Match ( have_nonrange_match, match_a_patch, match_a_patchread )+import Darcs.Flags ( DarcsFlag( Summary, DontGrabDeps, Verbose, DontPromptForDependencies), isInteractive )+import Darcs.Sealed ( FlippedSeal(..), flipSeal, seal2, unseal2 )+import Darcs.Utils ( askUser, promptCharFancy )+import Printer ( prefix, putDocLn )+#include "impossible.h"++data WhichChanges = Last | LastReversed | First | FirstReversed deriving (Eq, Show)++type MatchCriterion p = FORALL(u v) WhichChanges -> [DarcsFlag] -> (p C(u v)) -> Bool++type WithPatches p a C(x y) =+ String -- jobname+ -> [DarcsFlag] -- opts+ -> FL p C(x y) -- patches to select among+ -> ((FL p :> FL p) C(x y) -> IO a) -- job+ -> IO a -- result of running job++-- | The only difference with 'WithPatches' is the [FilePath] argument+type WithPatchesToFiles p a C(x y) =+ String -- jobname+ -> [DarcsFlag] -- opts+ -> [FilePath] -- files+ -> FL p C(x y) -- patches to select among+ -> ((FL p :> FL p) C(x y) -> IO a) -- job+ -> IO a -- result of running job++with_selected_changes'+ :: WithPatches Prim a C(x y)+with_selected_changes_to_files'+ :: WithPatchesToFiles Prim a C(x y)+with_selected_last_changes_to_files'+ :: WithPatchesToFiles Prim a C(x y)+with_selected_last_changes_reversed'+ :: WithPatches Prim a C(x y)++-- Common match criteria+triv :: MatchCriterion p+triv _ _ _ = True++iswanted :: Patchy p => MatchCriterion (PatchInfoAnd p)+iswanted First opts p = match_a_patch opts . hopefully $ p+iswanted LastReversed opts p = match_a_patch opts . hopefully . invert $ p+iswanted Last _ _ = bug "don't support patch matching with Last in wasp"+iswanted FirstReversed _ _ = bug "don't support patch matching with FirstReversed in wasp"++with_selected_changes' = wasc First triv+with_selected_changes_to_files' = wasc_ First triv+with_selected_last_changes_to_files' = wasc_ Last triv+with_selected_last_changes_reversed' = wasc LastReversed triv++with_selected_changes :: RepoPatch p => WithPatches (PatchInfoAnd p) a C(x y)+with_selected_changes_to_files :: RepoPatch p => WithPatchesToFiles (PatchInfoAnd p) a C(x y)+with_selected_changes_reversed :: RepoPatch p => WithPatches (PatchInfoAnd p) a C(x y)+with_selected_last_changes_to_files :: RepoPatch p => WithPatchesToFiles (PatchInfoAnd p) a C(x y)+with_selected_last_changes_to_files_reversed :: RepoPatch p => WithPatchesToFiles (PatchInfoAnd p) a C(x y)+with_selected_last_changes_reversed :: RepoPatch p => WithPatches (PatchInfoAnd p) a C(x y)++with_selected_changes = wasc First iswanted+with_selected_changes_to_files = wasc_ First iswanted+with_selected_changes_reversed = wasc FirstReversed iswanted+with_selected_last_changes_to_files = wasc_ Last iswanted+with_selected_last_changes_to_files_reversed = wasc_ LastReversed iswanted+with_selected_last_changes_reversed = wasc LastReversed iswanted++-- | wasc and wasc_ are just shorthand for with_any_selected_changes+wasc :: Patchy p => WhichChanges -> MatchCriterion p -> WithPatches p a C(x y)+wasc mwch crit j o = wasc_ mwch crit j o []+wasc_ :: Patchy p => WhichChanges -> MatchCriterion p -> WithPatchesToFiles p a C(x y)+wasc_ = with_any_selected_changes++with_any_selected_changes :: Patchy p => WhichChanges -> MatchCriterion p -> WithPatchesToFiles p a C(x y)+with_any_selected_changes Last crit jn opts fs =+ with_any_selected_changes_last+ (patches_to_consider_last' fs opts crit)+ crit jn opts fs+with_any_selected_changes First crit jn opts fs =+ with_any_selected_changes_first+ (patches_to_consider_first' fs opts crit)+ crit jn opts fs+with_any_selected_changes FirstReversed crit jn opts fs =+ with_any_selected_changes_first_reversed+ (patches_to_consider_first_reversed' fs opts crit)+ crit jn opts fs+with_any_selected_changes LastReversed crit jn opts fs =+ with_any_selected_changes_last_reversed+ (patches_to_consider_last_reversed' fs opts crit)+ crit jn opts fs+++view_changes :: RepoPatch p => [DarcsFlag] -> FL (PatchInfoAnd p) C(x y) -> IO ()+view_changes opts ps = do+ text_view opts Nothing 0 NilRL init_tps init_pc+ return ()+ where (init_pc, init_tps) = patch_choices_tps ps++data KeyPress a = KeyPress { kp :: Char+ , kpHelp :: String }++helpFor :: String -> [[KeyPress a]] -> String+helpFor jobname options =+ unlines $ [ "How to use "++jobname++":" ]+ ++ (concat $ intersperse [""] $ map (map help) options)+ ++ [ ""+ , "?: show this help"+ , ""+ , "<Space>: accept the current default (which is capitalized)"+ ]+ where help i = kp i:(": "++kpHelp i)++keysFor :: [[KeyPress a]] -> [Char]+keysFor = concatMap (map kp)++with_selected_patch_from_repo :: forall p C(r u t). RepoPatch p => String -> Repository p C(r u t) -> [DarcsFlag]+ -> (FORALL(a) (FL (PatchInfoAnd p) :> PatchInfoAnd p) C(a r) -> IO ()) -> IO ()+with_selected_patch_from_repo jn repository opts job = do+ p_s <- read_repo repository+ sp <- wspfr jn (match_a_patchread opts)+ (concatRL p_s) NilFL+ case sp of+ Just (FlippedSeal (skipped :> selected)) -> job (skipped :> selected)+ Nothing -> do putStrLn $ "Cancelling "++jn++" since no patch was selected."++-- | This ensures that the selected patch commutes freely with the skipped patches, including pending+-- and also that the skipped sequences has an ending context that matches the recorded state, z,+-- of the repository.+wspfr :: RepoPatch p => String -> (FORALL(a b) (PatchInfoAnd p) C(a b) -> Bool)+ -> RL (PatchInfoAnd p) C(x y) -> FL (PatchInfoAnd p) C(y u)+ -> IO (Maybe (FlippedSeal (FL (PatchInfoAnd p) :> (PatchInfoAnd p)) C(u)))+wspfr _ _ NilRL _ = return Nothing+wspfr jn matches (p:<:pps) skipped+ | not $ matches p = wspfr jn matches pps (p:>:skipped)+ | otherwise =+ case commuteFL (p :> skipped) of+ Left _ -> do putStrLn "\nSkipping depended-upon patch:"+ printFriendly [] p+ wspfr jn matches pps (p:>:skipped)+ Right (skipped' :> p') -> do+ printFriendly [] p+ let repeat_this = wspfr jn matches (p:<:pps) skipped+ options = [[ KeyPress 'y' (jn++" this patch")+ , KeyPress 'n' ("don't "++jn++" it")+ , KeyPress 'v' "view this patch in full"+ , KeyPress 'p' "view this patch in full with pager"+ , KeyPress 'x' "view a summary of this patch"+ , KeyPress 'q' ("cancel "++jn)+ ]]+ let prompt = "Shall I "++jn++" this patch?"+ yorn <- promptCharFancy prompt (keysFor options) (Just 'n') "?h"+ case yorn of+ 'y' -> return $ Just $ flipSeal $ skipped' :> p'+ 'n' -> wspfr jn matches pps (p:>:skipped)+ 'v' -> printPatch p >> repeat_this+ 'p' -> printPatchPager p >> repeat_this+ 'x' -> do putDocLn $ prefix " " $ summary p+ repeat_this+ 'q' -> do putStrLn $ jn_cap++" cancelled."+ exitWith $ ExitSuccess+ _ -> do putStrLn $ helpFor jn options+ repeat_this+ where jn_cap = (toUpper $ head jn) : tail jn++with_any_selected_changes_last :: forall p a C(x y). Patchy p+ => (FL p C(x y) -> (FL p :> FL p) C(x y))+ -> MatchCriterion p+ -> WithPatchesToFiles p a C(x y)+with_any_selected_changes_last p2c crit jobname opts _ ps job =+ case p2c ps of+ ps_to_consider :> other_ps ->+ if not $ isInteractive opts+ then job $ ps_to_consider :> other_ps+ else do pc <- tentatively_text_select "" jobname (Noun "patch") Last crit+ opts ps_len 0 NilRL init_tps init_pc+ job $ selected_patches_last rejected_ps pc+ where rejected_ps = ps_to_consider+ ps_len = lengthFL init_tps+ (init_pc, init_tps) = patch_choices_tps $ other_ps++with_any_selected_changes_first :: forall p a C(x y). Patchy p+ => (FL p C(x y) -> (FL p :> FL p) C(x y))+ -> MatchCriterion p+ -> WithPatchesToFiles p a C(x y)+with_any_selected_changes_first p2c crit jobname opts _ ps job =+ case p2c ps of+ ps_to_consider :> other_ps ->+ if not $ isInteractive opts+ then job $ ps_to_consider :> other_ps+ else do pc <- tentatively_text_select "" jobname (Noun "patch") First crit+ opts ps_len 0 NilRL init_tps init_pc+ job $ selected_patches_first rejected_ps pc+ where rejected_ps = other_ps+ ps_len = lengthFL init_tps+ (init_pc, init_tps) = patch_choices_tps $ ps_to_consider++with_any_selected_changes_first_reversed :: forall p a C(x y). Patchy p+ => (FL p C(x y) -> (FL p :> FL p) C(y x))+ -> MatchCriterion p+ -> WithPatchesToFiles p a C(x y)+with_any_selected_changes_first_reversed p2c crit jobname opts _ ps job =+ case p2c ps of+ ps_to_consider :> other_ps ->+ if not $ isInteractive opts+ then job $ invert other_ps :> invert ps_to_consider+ else do pc <- tentatively_text_select "" jobname (Noun "patch") FirstReversed crit+ opts ps_len 0 NilRL init_tps init_pc+ job $ selected_patches_first_reversed rejected_ps pc+ where rejected_ps = ps_to_consider+ ps_len = lengthFL init_tps+ (init_pc, init_tps) = patch_choices_tps other_ps++with_any_selected_changes_last_reversed :: forall p a C(x y). Patchy p+ => (FL p C(x y) -> (FL p :> FL p) C(y x))+ -> MatchCriterion p+ -> WithPatchesToFiles p a C(x y)+with_any_selected_changes_last_reversed p2c crit jobname opts _ ps job =+ case p2c ps of+ ps_to_consider :> other_ps ->+ if not $ isInteractive opts+ then job $ invert other_ps :> invert ps_to_consider+ else do pc <- tentatively_text_select "" jobname (Noun "patch") LastReversed crit+ opts ps_len 0 NilRL init_tps init_pc+ job $ selected_patches_last_reversed rejected_ps pc+ where rejected_ps = other_ps+ ps_len = lengthFL init_tps+ (init_pc, init_tps) = patch_choices_tps ps_to_consider+++patches_to_consider_first' :: Patchy p+ => [FilePath] -- ^ files+ -> [DarcsFlag] -- ^ opts+ -> MatchCriterion p+ -> FL p C(x y) -- ^ patches+ -> (FL p :> FL p) C(x y)+patches_to_consider_first' fs opts crit ps =+ let deselect_unwanted pc =+ if have_nonrange_match opts+ then if DontGrabDeps `elem` opts+ then force_matching_last (not.iswanted_) pc+ else make_everything_later $ force_matching_first iswanted_ pc+ else pc+ iswanted_ = crit First opts . tp_patch+ in if null fs && not (have_nonrange_match opts)+ then ps :> NilFL+ else tp_patches $ separate_first_middle_from_last $ deselect_not_touching fs+ $ deselect_unwanted $ patch_choices ps++patches_to_consider_last' :: Patchy p+ => [FilePath] -- ^ files+ -> [DarcsFlag] -- ^ opts+ -> MatchCriterion p+ -> FL p C(x y) -- ^ patches+ -> (FL p :> FL p) C(x y)+patches_to_consider_last' fs opts crit ps =+ let deselect_unwanted pc =+ if have_nonrange_match opts+ then if DontGrabDeps `elem` opts+ then force_matching_last (not.iswanted_) pc+ else make_everything_later $ force_matching_first iswanted_ pc+ else pc+ iswanted_ = crit Last opts . tp_patch+ in if null fs && not (have_nonrange_match opts)+ then NilFL :> ps+ else case get_choices $ select_not_touching fs $ deselect_unwanted $ patch_choices ps of+ fc :> mc :> lc -> tp_patches $ fc :> mc +>+ lc++patches_to_consider_first_reversed' :: Patchy p+ => [FilePath] -- ^ files+ -> [DarcsFlag] -- ^ opts+ -> MatchCriterion p+ -> FL p C(x y) -- ^ patches+ -> (FL p :> FL p) C(y x)+patches_to_consider_first_reversed' fs opts crit ps =+ let deselect_unwanted pc =+ if have_nonrange_match opts+ then if DontGrabDeps `elem` opts+ then force_matching_last (not.iswanted_) pc+ else make_everything_later $ force_matching_first iswanted_ pc+ else pc+ iswanted_ = crit FirstReversed opts . tp_patch+ in if null fs && not (have_nonrange_match opts)+ then NilFL :> (invert ps)+ else case get_choices $ select_not_touching fs $ deselect_unwanted $ patch_choices $ invert ps of+ fc :> mc :> lc -> tp_patches $ fc :> mc +>+ lc++patches_to_consider_last_reversed' :: Patchy p+ => [FilePath] -- ^ files+ -> [DarcsFlag] -- ^ opts+ -> MatchCriterion p+ -> FL p C(x y) -- ^ patches+ -> (FL p :> FL p) C(y x)+patches_to_consider_last_reversed' fs opts crit ps =+ let deselect_unwanted pc =+ if have_nonrange_match opts+ then if DontGrabDeps `elem` opts+ then force_matching_last (not.iswanted_) pc+ else make_everything_later $ force_matching_first iswanted_ pc+ else pc+ iswanted_ = crit LastReversed opts . tp_patch+ in+ if null fs && not (have_nonrange_match opts)+ then (invert ps) :> NilFL+ else tp_patches $ separate_first_middle_from_last $ deselect_not_touching fs+ $ deselect_unwanted $ patch_choices $ invert ps++-- | Returns the results of a patch selection user interaction+selected_patches_last :: Patchy p => FL p C(x y) -> PatchChoices p C(y z)+ -> (FL p :> FL p) C(x z)+selected_patches_last other_ps pc =+ case get_choices pc of+ fc :> mc :> lc -> other_ps +>+ mapFL_FL tp_patch (fc +>+ mc) :> mapFL_FL tp_patch lc++selected_patches_first :: Patchy p => FL p C(y z) -> PatchChoices p C(x y)+ -> (FL p :> FL p) C(x z)+selected_patches_first other_ps pc =+ case separate_first_from_middle_last pc of+ xs :> ys -> mapFL_FL tp_patch xs :> mapFL_FL tp_patch ys +>+ other_ps++selected_patches_last_reversed :: Patchy p => FL p C(y x) -> PatchChoices p C(z y)+ -> (FL p :> FL p) C(x z)+selected_patches_last_reversed other_ps pc =+ case separate_first_from_middle_last pc of+ xs :> ys -> invert (mapFL_FL tp_patch ys +>+ other_ps) :> invert (mapFL_FL tp_patch xs)++selected_patches_first_reversed :: Patchy p => FL p C(z y) -> PatchChoices p C(y x)+ -> (FL p :> FL p) C(x z)+selected_patches_first_reversed other_ps pc =+ case get_choices pc of+ fc :> mc :> lc -> invert (mapFL_FL tp_patch lc) :> invert (other_ps +>+ mapFL_FL tp_patch (fc +>+ mc))++text_select :: forall p C(x y z). Patchy p => String -> WhichChanges+ -> MatchCriterion p -> [DarcsFlag] -> Int -> Int+ -> RL (TaggedPatch p) C(x y) -> FL (TaggedPatch p) C(y z) -> PatchChoices p C(x z)+ -> IO ((PatchChoices p) C(x z))++text_select _ _ _ _ _ _ _ NilFL pc = return pc+text_select jn whichch crit opts n_max n+ tps_done tps_todo@(tp:>:tps_todo') pc = do+ (printFriendly opts) `unseal2` viewp+ repeat_this -- prompt the user+ where+ do_next_action ja je = tentatively_text_select ja jn je whichch crit opts+ n_max+ (n+1) (tp:<:tps_done) tps_todo'+ do_next = do_next_action "" (Noun "patch")+ helper :: PatchChoices p C(a b) -> p C(a b)+ helper = undefined+ thing = Darcs.Patch.thing (helper pc)+ things = Darcs.Patch.things (helper pc)+ options_basic =+ [ KeyPress 'y' (jn++" this "++thing)+ , KeyPress 'n' ("don't "++jn++" it")+ , KeyPress 'w' ("wait and decide later, defaulting to no") ]+ options_file =+ [ KeyPress 's' ("don't "++jn++" the rest of the changes to this file")+ , KeyPress 'f' (jn++" the rest of the changes to this file") ]+ options_view =+ [ KeyPress 'v' ("view this "++thing++" in full")+ , KeyPress 'p' ("view this "++thing++" in full with pager")+ , KeyPress 'l' ("list all selected "++things) ]+ options_summary =+ [ KeyPress 'x' ("view a summary of this "++thing) ]+ options_quit =+ [ KeyPress 'd' (jn++" selected "++things++", skipping all the remaining "++things)+ , KeyPress 'a' (jn++" all the remaining "++things)+ , KeyPress 'q' ("cancel "++jn) ]+ options_nav =+ [ KeyPress 'j' ("skip to next "++thing)+ , KeyPress 'k' ("back up to previous "++thing) ]+ options = [options_basic]+ ++ (if is_single_file_patch then [options_file] else [])+ ++ [options_view +++ if Summary `elem` opts then [] else options_summary]+ ++ [options_quit]+ ++ [options_nav ]+ prompt = "Shall I "++jn++" this "++thing++"? "+ ++ "(" ++ show (n+1) ++ "/" ++ show n_max ++ ") "+ repeat_this :: IO ((PatchChoices p) C(x z))+ repeat_this = do+ yorn <- promptCharFancy prompt (keysFor options) (Just the_default) "?h"+ case yorn of+ 'y' -> do_next $ force_yes (tag tp) pc+ 'n' -> do_next $ force_no (tag tp) pc+ 'w' -> do_next $ make_uncertain (tag tp) pc+ 's' -> do_next_action "Skipped" (Noun "change") $ skip_file+ 'f' -> do_next_action "Included" (Noun "change") $ do_file+ 'v' -> printPatch `unseal2` viewp >> repeat_this+ 'p' -> printPatchPager `unseal2` viewp >> repeat_this+ 'l' -> do let selected = case get_choices pc of+ (first_chs:>_:>last_chs) ->+ if whichch == Last || whichch == FirstReversed+ then map_patches last_chs+ else map_patches first_chs+ map_patches = mapFL (\a ->+ (showFriendly opts) `unseal2` (seal2 $ tp_patch a))+ putStrLn $ "---- Already selected "++things++" ----"+ mapM_ putDocLn $ selected+ putStrLn $ "---- end of already selected "++things++" ----"+ (printFriendly opts) `unseal2` viewp+ repeat_this+ 'x' -> do (putDocLn . prefix " " . summary) `unseal2` viewp+ repeat_this+ 'd' -> return pc+ 'a' -> do ask_confirmation+ return $ select_all_middles (whichch == Last || whichch == FirstReversed) pc+ 'q' -> do putStrLn $ jn_cap++" cancelled."+ exitWith $ ExitSuccess+ 'j' -> case tps_todo' of+ NilFL -> -- May as well work out the length now we have all+ -- the patches in memory+ text_select jn whichch crit opts+ n_max n tps_done tps_todo pc+ _ -> text_select jn whichch crit opts+ n_max (n+1) (tp:<:tps_done) tps_todo' pc+ 'k' -> case tps_done of+ NilRL -> repeat_this+ (tp':<:tps_done') ->+ text_select jn whichch crit opts+ n_max (n-1) tps_done' (tp':>:tps_todo) pc+ 'c' -> text_select jn whichch crit opts+ n_max n tps_done tps_todo pc+ _ -> do putStrLn $ helpFor jn options+ repeat_this+ force_yes = if whichch == Last || whichch == FirstReversed then force_last else force_first+ force_no = if whichch == Last || whichch == FirstReversed then force_first else force_last+ patches_to_skip = (tag tp:) $ catMaybes+ $ mapFL (\tp' -> if list_touched_files tp' == touched_files+ then Just (tag tp')+ else Nothing) tps_todo'+ skip_file = foldr force_no pc patches_to_skip+ do_file = foldr force_yes pc patches_to_skip+ the_default = get_default (whichch == Last || whichch == FirstReversed) $ patch_slot tp pc+ jn_cap = (toUpper $ head jn) : tail jn+ touched_files = list_touched_files $ tp_patch tp+ is_single_file_patch = length touched_files == 1+ viewp = if whichch == LastReversed || whichch == FirstReversed then seal2 $ invert (tp_patch tp) else seal2 $ tp_patch tp+ ask_confirmation =+ if jn `elem` ["unpull", "unrecord", "obliterate"]+ then do yorn <- askUser $ "Really " ++ jn ++ " all undecided patches? "+ case yorn of+ ('y':_) -> return ()+ _ -> exitWith $ ExitSuccess+ else return ()++text_view :: forall p C(x y u r s). Patchy p => [DarcsFlag] -> Maybe Int -> Int+ -> RL (TaggedPatch p) C(x y) -> FL (TaggedPatch p) C(y u) -> PatchChoices p C(r s)+ -> IO ((PatchChoices p) C(r s))+text_view _ _ _ _ NilFL _ = return $ patch_choices $ unsafeCoerceP NilFL --return pc+text_view opts n_max n+ tps_done tps_todo@(tp:>:tps_todo') pc = do+ printFriendly opts (tp_patch tp)+ putStr "\n"+ repeat_this -- prompt the user+ where+ prev_patch :: IO ((PatchChoices p) C(r s))+ prev_patch = case tps_done of+ NilRL -> repeat_this+ (tp':<:tps_done') ->+ text_view opts+ n_max (n-1) tps_done' (tp':>:tps_todo) pc+ next_patch :: IO ((PatchChoices p) C(r s))+ next_patch = case tps_todo' of+ NilFL -> -- May as well work out the length now we have all+ -- the patches in memory+ text_view opts n_max+ n tps_done NilFL pc+ _ -> text_view opts n_max+ (n+1) (tp:<:tps_done) tps_todo' pc+ options_yn =+ [ KeyPress 'y' "view this patch and go to the next"+ , KeyPress 'n' "skip to the next patch" ]+ options_view =+ [ KeyPress 'v' "view this patch in full"+ , KeyPress 'p' "view this patch in full with pager" ]+ options_summary =+ [ KeyPress 'x' "view a summary of this patch" ]+ options_nav =+ [ KeyPress 'q' ("quit view changes")+ , KeyPress 'k' "back up to previous patch"+ , KeyPress 'j' "skip to next patch"+ , KeyPress 'c' "count total patch number" ]+ options = [ options_yn ]+ ++ [ options_view +++ if Summary `elem` opts then [] else options_summary ]+ ++ [ options_nav ]+ prompt = "Shall I view this patch? "+ ++ "(" ++ show (n+1) ++ "/" ++ maybe "?" show n_max ++ ")"+ repeat_this :: IO ((PatchChoices p) C(r s))+ repeat_this = do+ yorn <- promptCharFancy prompt (keysFor options) (Just 'n') "?h"+ case yorn of+ 'y' -> printPatch (tp_patch tp) >> next_patch+ 'n' -> next_patch+ 'v' -> printPatch (tp_patch tp) >> repeat_this+ 'p' -> printPatchPager (tp_patch tp) >> repeat_this+ 'x' -> do putDocLn $ prefix " " $ summary (tp_patch tp)+ repeat_this+ 'q' -> exitWith ExitSuccess+ 'k' -> prev_patch+ 'j' -> next_patch+ 'c' -> text_view opts+ count_n_max n tps_done tps_todo pc+ _ -> do putStrLn $ helpFor "view changes" options+ repeat_this+ count_n_max | isJust n_max = n_max+ | otherwise = Just $ lengthFL tps_todo + lengthRL tps_done+tentatively_text_select :: Patchy p => String -> String -> Noun -> WhichChanges+ -> MatchCriterion p -> [DarcsFlag]+ -> Int -> Int -> RL (TaggedPatch p) C(x y) -> FL (TaggedPatch p) C(y z)+ -> PatchChoices p C(x z)+ -> IO ((PatchChoices p) C(x z))+tentatively_text_select _ _ _ _ _ _ _ _ _ NilFL pc = return pc+tentatively_text_select jobaction jobname jobelement whichch crit+ opts n_max n ps_done ps_todo pc =+ case spanFL (\p -> decided $ patch_slot p pc) ps_todo of+ skipped :> unskipped -> do+ when (numSkipped > 0) show_skipped+ let (boringThenInteresting) =+ if DontPromptForDependencies `elem` opts+ then spanFL (not.(crit whichch opts).tp_patch) unskipped+ else NilFL :> unskipped+ case boringThenInteresting of+ boring :> interesting -> do+ let numNotConsidered = lengthFL boring + numSkipped+ text_select jobname whichch crit opts n_max (n + numNotConsidered)+ (reverseFL boring +<+ reverseFL skipped +<+ ps_done) interesting pc+ where+ numSkipped = lengthFL skipped+ show_skipped = do putStrLn $ _doing_ ++ _with_ ++ "."+ when (Verbose `elem` opts) $ showskippedpatch skipped+ where+ _doing_ = _action_ ++ " " ++ jobname+ _with_ = " of " ++ show numSkipped ++ " " ++ _elem_ ""+ _action_ = if (length jobaction) == 0 then "Skipped" else jobaction+ _elem_ = englishNum numSkipped jobelement+ showskippedpatch :: Patchy p => FL (TaggedPatch p) C(y t) -> IO ()+ showskippedpatch (tp:>:tps) = (putDocLn $ prefix " " $ summary (tp_patch tp)) >> showskippedpatch tps+ showskippedpatch NilFL = return ()++decided :: Slot -> Bool+decided InMiddle = False+decided _ = True++get_default :: Bool -> Slot -> Char+get_default _ InMiddle = 'w'+get_default True InFirst = 'n'+get_default True InLast = 'y'+get_default False InFirst = 'y'+get_default False InLast = 'n'++tp_patches :: (FL (TaggedPatch p) :> FL (TaggedPatch p)) C(x y)+ -> (FL p :> FL p) C(x y)+tp_patches (x:>y) = mapFL_FL tp_patch x :> mapFL_FL tp_patch y
@@ -0,0 +1,25 @@+{-# OPTIONS_GHC -cpp -fglasgow-exts #-}+{-# LANGUAGE CPP #-}++module Darcs.Show(Show1(..), Show2(..), showOp2, app_prec) where++#include "gadts.h"++class Show1 a where+ show1 :: a C(x) -> String+ show1 x = showsPrec1 0 x ""+ showsPrec1 :: Int -> a C(x) -> ShowS+ showsPrec1 _ x s = show1 x ++ s++class Show2 a where+ show2 :: a C(x y) -> String+ show2 x = showsPrec2 0 x ""+ showsPrec2 :: Int -> a C(x y) -> ShowS+ showsPrec2 _ x s = show2 x ++ s++showOp2 :: (Show2 a, Show2 b) => Int -> String -> Int -> a C(w x) -> b C(y z) -> String -> String+showOp2 prec opstr d x y = showParen (d > prec) $ showsPrec2 (prec + 1) x .+ showString opstr . showsPrec2 (prec + 1) y++app_prec :: Int+app_prec = 10
@@ -0,0 +1,132 @@+-- Copyright (C) 2003 David Roundy+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.++{-# OPTIONS_GHC -cpp -fglasgow-exts #-}+{-# LANGUAGE CPP #-}+-- , DeriveDataTypeable #-}++module Darcs.SignalHandler ( withSignalsHandled, withSignalsBlocked,+ catchInterrupt, catchNonSignal,+ tryNonSignal, stdout_is_a_pipe ) where++import System.IO.Error ( isUserError, ioeGetErrorString, ioeGetFileName )+import Control.Exception ( dynExceptions, ioErrors, catchJust, Exception ( IOException ) )+import System.Exit ( exitWith, ExitCode ( ExitFailure ) )+import Control.Concurrent ( ThreadId, myThreadId )+import Control.Exception ( catchDyn, throwDyn, throwDynTo, block )+import System.Posix.Files ( getFdStatus, isNamedPipe )+import System.Posix.IO ( stdOutput )+import Data.Dynamic ( Typeable, fromDynamic )+import System.IO ( hPutStrLn, stderr )+import Control.Monad ( when )++import Workaround ( installHandler, raiseSignal, Handler(..), Signal,+ sigINT, sigHUP, sigABRT, sigALRM, sigTERM, sigPIPE )+#ifdef WIN32+import CtrlC ( withCtrlCHandler )+#endif++stdout_is_a_pipe :: IO Bool+stdout_is_a_pipe+ = catchJust ioErrors+ (do stat <- getFdStatus stdOutput+ return (isNamedPipe stat))+ (\_ -> return False)++withSignalsHandled :: IO a -> IO a+newtype SignalException = SignalException Signal deriving (Typeable)++withSignalsHandled job = do+ thid <- myThreadId+ mapM_ (ih thid) [sigINT, sigHUP, sigABRT, sigTERM, sigPIPE]+ catchJust just_usererrors (job' thid `catchSignal` defaults)+ die_with_string+ where defaults s | s == sigINT = ew s "Interrupted!"+ | s == sigHUP = ew s "HUP"+ | s == sigABRT = ew s "ABRT"+ | s == sigTERM = ew s "TERM"+ | s == sigPIPE = exitWith $ ExitFailure $ 1+ | otherwise = ew s "Unhandled signal!"+ ew sig s = do hPutStrLn stderr $ ("withSignalsHandled: " ++ s)+ resethandler sig+ raiseSignal sig -- ensure that our caller knows how we died+ exitWith $ ExitFailure $ 1+ die_with_string e | take 6 e == "STDOUT" =+ do is_pipe <- stdout_is_a_pipe+ when (not is_pipe) $+ hPutStrLn stderr $ "\ndarcs failed: "++drop 6 e+ exitWith $ ExitFailure $ 2+ die_with_string e = do hPutStrLn stderr $ "\ndarcs failed: "++e+ exitWith $ ExitFailure $ 2+#ifdef WIN32+ job' thid =+ withCtrlCHandler (throwDynTo thid $ SignalException sigINT) job+#else+ job' _ = job+#endif++resethandler :: Signal -> IO ()+resethandler s = do installHandler s Default Nothing+ return ()++ih :: ThreadId -> Signal -> IO ()+ih thid s =+ do installHandler s (Catch $ throwDynTo thid $ SignalException s) Nothing+ return ()++catchSignal :: IO a -> (Signal -> IO a) -> IO a+catchSignal job handler =+ job `Control.Exception.catchDyn` (\(SignalException sig) -> handler sig)++-- catchNonSignal is a drop-in replacement for Control.Exception.catch, which allows+-- us to catch anything but a signal. Useful for situations where we want+-- don't want to inhibit ctrl-C.++catchNonSignal :: IO a -> (Control.Exception.Exception -> IO a) -> IO a+catchNonSignal = Control.Exception.catchJust notSig+ where notSig x = case dynExceptions x of+ Nothing -> Just x+ Just d -> case fromDynamic d :: Maybe SignalException of+ Just _ -> Nothing+ Nothing -> Just x++catchInterrupt :: IO a -> IO a -> IO a+catchInterrupt job handler =+ job `catchSignal` h+ where h s | s == sigINT = handler+ | otherwise = throwDyn (SignalException s)++tryNonSignal :: IO a -> IO (Either Control.Exception.Exception a)+tryNonSignal j = (Right `fmap` j) `catchNonSignal` \e -> return (Left e)++just_usererrors :: Control.Exception.Exception -> Maybe String+just_usererrors (IOException e) | isUserError e = Just $ ioeGetErrorString e+just_usererrors (IOException e) | ioeGetFileName e == Just "<stdout>"+ = Just $ "STDOUT"++ioeGetErrorString e+just_usererrors _ = Nothing++withSignalsBlocked :: IO () -> IO ()+withSignalsBlocked job = (block job) `catchSignal` couldnt_do+ where couldnt_do s | s == sigINT = oops "interrupt"+ | s == sigHUP = oops "HUP"+ | s == sigABRT = oops "ABRT"+ | s == sigALRM = oops "ALRM"+ | s == sigTERM = oops "TERM"+ | s == sigPIPE = return ()+ | otherwise = oops "unknown signal"+ oops s = hPutStrLn stderr $ "Couldn't handle " ++ s +++ " since darcs was in a sensitive job."
@@ -0,0 +1,36 @@+-- Copyright (C) 2002-2004 David Roundy+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.++module Darcs.SlurpDirectory+ (Slurpy, empty_slurpy, slurp,+ mmap_slurp, co_slurp, slurp_unboring,+ FileContents,+ undefined_time, undefined_size,+ doesFileReallyExist, doesDirectoryReallyExist, isFileReallySymlink,+ wait_a_moment,+ is_dir, is_file,+ get_slurp, slurp_name,+ slurp_has, slurp_has_anycase, slurp_hasfile, slurp_hasdir,+ list_slurpy, list_slurpy_files, list_slurpy_dirs, get_path_list,+ get_filecontents, get_dircontents, get_mtime, get_length,+ slurp_modfile, slurp_remove, slurp_removefile, slurp_removedir,+ write_files,+ SlurpMonad, withSlurpy, writeSlurpy, syncSlurpy+ )+ where++import Darcs.SlurpDirectory.Internal
@@ -0,0 +1,677 @@+-- Copyright (C) 2002-2004 David Roundy+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.++{-# OPTIONS_GHC -cpp #-}+{-# LANGUAGE CPP #-}++-- | SlurpDirectory is intended to give a nice lazy way of traversing directory+-- trees.+module Darcs.SlurpDirectory.Internal+ ( Slurpy(..), SlurpyContents(..), slurpies_to_map, map_to_slurpies,+ FileContents, empty_slurpy,+ slurp, mmap_slurp, slurp_unboring, co_slurp,+ slurp_name, is_file, is_dir,+ get_filecontents, get_dircontents, get_mtime,+ get_length, get_slurp,+ slurp_removefile, slurp_removedir,+ slurp_remove,+ slurp_modfile, slurp_hasfile, slurp_hasdir,+ slurp_has_anycase, wait_a_moment, undefined_time,+ undefined_size,+ slurp_has, list_slurpy, list_slurpy_files,+ get_path_list,+ list_slurpy_dirs,+ isFileReallySymlink,+ doesFileReallyExist, doesDirectoryReallyExist,+ SlurpMonad, withSlurpy, write_files,+ writeSlurpy, syncSlurpy+ ) where++import System.IO+import System.Directory hiding ( getCurrentDirectory, renameFile )+import Workaround ( getCurrentDirectory )+import Darcs.Utils ( withCurrentDirectory, formatPath )+import Darcs.RepoPath ( FilePathLike, toPath )+import System.IO.Unsafe ( unsafeInterleaveIO )+import Data.List ( isPrefixOf )+import Control.Monad ( MonadPlus(..), when )+import Data.Char ( toLower )+import System.Posix.Types ( EpochTime )+import System.Posix.Files+ ( getSymbolicLinkStatus, modificationTime,+ fileSize,+ isRegularFile, isDirectory, isSymbolicLink+ )+import System.Posix ( sleep )+import Data.Maybe ( catMaybes, isJust, maybeToList )+import Data.Map (Map)+import qualified Data.Map as Map++import Darcs.SignalHandler ( tryNonSignal )+import Darcs.CheckFileSystem ( can_I_use_mmap )+import Darcs.IO ( ReadableDirectory(..), WriteableDirectory(..) )++import ByteStringUtils+import qualified Data.ByteString as B++import Darcs.Patch.FileName ( FileName, fn2fp, fp2fn, norm_path, break_on_dir,+ own_name, super_name )+#if mingw32_HOST_OS+import Data.Int ( Int64 )+#else+import System.Posix.Types ( FileOffset )+#endif++#include "impossible.h"++#if mingw32_HOST_OS+type FileOffset = Int64+#endif++data Slurpy = Slurpy !FileName !SlurpyContents++slurpy_to_pair :: Slurpy -> (FileName, SlurpyContents)+slurpy_to_pair (Slurpy fn sc) = (fn, sc)++pair_to_slurpy :: (FileName, SlurpyContents) -> Slurpy+pair_to_slurpy = uncurry Slurpy++type SlurpyMap = Map FileName SlurpyContents++slurpies_to_map :: [Slurpy] -> SlurpyMap+slurpies_to_map = Map.fromList . map slurpy_to_pair++map_to_slurpies :: SlurpyMap -> [Slurpy]+map_to_slurpies = map pair_to_slurpy . Map.toList++data SlurpyContents = SlurpDir (Maybe String) SlurpyMap+ | SlurpFile (Maybe String,EpochTime,FileOffset) FileContents+type FileContents = B.ByteString++instance Show Slurpy where+ show (Slurpy fn (SlurpDir _ l)) =+ "Dir " ++ (fn2fp fn) ++ "\n" +++ concat (map show $ map_to_slurpies l) ++ "End Dir " ++ (fn2fp fn) ++ "\n"+ show (Slurpy fn (SlurpFile _ _)) = "File " ++ (fn2fp fn) ++ "\n"++mapSlurpyNames :: (FileName -> FileName) -> Slurpy -> Slurpy+mapSlurpyNames f = onSlurpy+ where onSlurpy (Slurpy fn sc) = Slurpy (f fn) (onSlurpyContents sc)+ onSlurpyContents sf@(SlurpFile _ _) = sf+ onSlurpyContents (SlurpDir x sm) = SlurpDir x . slurpies_to_map . map onSlurpy . map_to_slurpies $ sm++slurp :: FilePathLike p => p -> IO Slurpy+mmap_slurp :: FilePath -> IO Slurpy+slurp_unboring :: (FilePath->Bool) -> FilePath -> IO Slurpy+empty_slurpy :: Slurpy+empty_slurpy = Slurpy (fp2fn ".") (SlurpDir Nothing Map.empty)+slurp_name :: Slurpy -> FilePath+is_file :: Slurpy -> Bool+is_dir :: Slurpy -> Bool++get_filecontents :: Slurpy -> FileContents+get_dircontents :: Slurpy -> [Slurpy]+get_mtime :: Slurpy -> EpochTime+get_length :: Slurpy -> FileOffset++instance Eq Slurpy where+ s1 == s2 = (slurp_name s1) == (slurp_name s2)+instance Ord Slurpy where+ s1 <= s2 = (slurp_name s1) <= (slurp_name s2)++data SlurpMonad a = SM ((Either String Slurpy)+ -> Either String (Slurpy, a))+mksm :: (Slurpy -> Either String (Slurpy, a)) -> SlurpMonad a+mksm x = SM sm where sm (Left e) = Left e+ sm (Right s) = x s++instance Functor SlurpMonad where+ fmap f m = m >>= return . f++instance Monad SlurpMonad where+ (SM p) >>= k = SM sm+ where sm e = case p e of+ Left er -> Left er+ Right (s, a) -> case k a of+ (SM q) -> q (Right s)+ return a = SM ( \s -> case s of+ Left e -> Left e+ Right x -> Right (x, a) )+ fail e = SM ( \s -> case s of+ Left x -> Left x+ _ -> Left e )++instance MonadPlus SlurpMonad where+ mzero = fail "SlurpMonad mzero"+ (SM p) `mplus` (SM q) = SM sm+ where sm e = case p e of+ Left _ -> q e+ okay -> okay++instance ReadableDirectory SlurpMonad where+ mDoesDirectoryExist d = smDoesDirectoryExist d+ mDoesFileExist f = smDoesFileExist f+ mInCurrentDirectory = smInSlurpy+ mGetDirectoryContents = smGetDirContents+ mReadFilePS = smReadFilePS+ mReadFilePSs = smReadFilePSs++instance WriteableDirectory SlurpMonad where+ mWithCurrentDirectory = modifySubSlurpy+ mSetFileExecutable _ _ = return ()+ mWriteFilePS = smWriteFilePS+ mCreateDirectory = smCreateDirectory+ mRename = smRename+ mRemoveDirectory = smRemoveDirectory+ mRemoveFile = smRemoveFile++write_file :: Slurpy -> FileName -> IO ()+write_file s fn = case withSlurpy s $ smReadFilePS fn of+ Left err -> fail err+ Right (_, c) -> do+ ensureDirectories (super_name fn)+ mWriteFilePS fn c+ +try_write_file :: Slurpy -> FilePath -> IO ()+try_write_file s fp = let fn = fp2fn fp in+ if slurp_hasfile fn s+ then write_file s fn+ else if slurp_hasdir fn s+ then ensureDirectories fn+ else return ()++ensureDirectories :: WriteableDirectory m => FileName -> m ()+ensureDirectories d = do+ isPar <- mDoesDirectoryExist d+ if isPar + then return ()+ else ensureDirectories (super_name d) >> (mCreateDirectory d)++write_files :: Slurpy -> [FilePath] -> IO ()+write_files s fps = mapM_ (try_write_file s) fps++-- don't overwrite non-empty directories unless explicitly asked by+-- being passed "." (which always exists)+writeSlurpy :: Slurpy -> FilePath -> IO ()+writeSlurpy s d = do+ when (d /= ".") $ createDirectory d+ withCurrentDirectory d $ write_files s (list_slurpy s)++withSlurpy :: Slurpy -> SlurpMonad a -> Either String (Slurpy, a)+withSlurpy s (SM f) = f (Right s)++smDoesDirectoryExist :: FileName -> SlurpMonad Bool+smDoesDirectoryExist d = mksm $ \s -> (Right (s, slurp_hasdir d s))++smDoesFileExist :: FileName -> SlurpMonad Bool+smDoesFileExist f = mksm $ \s -> (Right (s, slurp_hasfile f s))++-- smInSlurpy doesn't make any changes to the subdirectory.+smInSlurpy :: FileName -> SlurpMonad a -> SlurpMonad a+smInSlurpy d job = mksm sm+ where sm s = case get_slurp d s of+ Just s' | is_dir s' -> case withSlurpy s' job of+ Left e -> Left e+ Right (_,a) -> Right (s, a)+ _ -> Left $ "smInSlurpy: Couldn't find directory " +++ formatPath (fn2fp d)++fromSlurpFile :: FileName -> (Slurpy -> a) -> SlurpMonad a+fromSlurpFile f job = mksm sm+ where sm s = case get_slurp f s of+ Just s' | is_file s' -> Right (s, job s')+ _ -> Left $ "fromSlurpFile: Couldn't find file " +++ formatPath (fn2fp f)++modifySubSlurpy :: FileName -> SlurpMonad a -> SlurpMonad a+modifySubSlurpy d job = mksm sm+ where sm s = case get_slurp_context d s of+ Just (ctx, sub@(Slurpy _ (SlurpDir _ _))) ->+ case withSlurpy sub job of+ Left e -> Left e+ Right (sub',a) -> Right (ctx sub', a)+ _ -> Left $ "modifySubSlurpy: Couldn't find directory " +++ formatPath (fn2fp d)++modifyFileSlurpy :: FileName -> (Slurpy -> Slurpy) -> SlurpMonad ()+modifyFileSlurpy f job = mksm sm+ where sm s = case get_slurp_context f s of+ Just (ctx, sf@(Slurpy _ (SlurpFile _ _))) -> Right (ctx $ job sf, ())+ _ -> Left $ "modifyFileSlurpy: Couldn't find file " +++ formatPath (fn2fp f)++insertSlurpy :: FileName -> Slurpy -> SlurpMonad ()+insertSlurpy f news = mksm $ \s ->+ if slurp_hasfile f s || slurp_hasdir f s || not (slurp_hasdir (super_name f) s)+ then Left $ "Error creating file "++fn2fp f+ else Right (addslurp f news s, ())++smReadFilePS :: FileName -> SlurpMonad B.ByteString+smReadFilePS f = fromSlurpFile f get_filecontents++smReadFilePSs :: FileName -> SlurpMonad [B.ByteString]+smReadFilePSs f = fromSlurpFile f (linesPS . get_filecontents)++smGetDirContents :: SlurpMonad [FileName]+smGetDirContents = mksm $ \s -> Right (s, map slurp_fn $ get_dircontents s)++smWriteFilePS :: FileName -> B.ByteString -> SlurpMonad ()+smWriteFilePS f ps = -- this implementation could be made rather more direct+ -- and limited to a single pass down the Slurpy+ modifyFileSlurpy f (\_ -> sl)+ `mplus` insertSlurpy f sl+ where sl = Slurpy (own_name f) (SlurpFile undef_time_size ps)++smCreateDirectory :: FileName -> SlurpMonad ()+smCreateDirectory a = mksm sm+ where sm s = case slurp_adddir a s of+ Just s' -> Right (s', ())+ Nothing -> Left $ "Error creating directory "++fn2fp a++smRename :: FileName -> FileName -> SlurpMonad ()+smRename a b = mksm sm+ where sm s = case slurp_move a b s of+ Just s' -> Right (s', ())+ Nothing -> + -- Workaround for some old patches having moves when the source file doesn't exist.+ if (slurp_has (fn2fp a) s)+ then Left $ "Error moving "++fn2fp a++" to "++fn2fp b+ else Right (s, ())++smRemove :: FileName -> SlurpMonad ()+smRemove f = mksm sm+ where sm s = case slurp_remove f s of+ Nothing -> Left $ fn2fp f++" does not exist."+ Just s' -> Right (s', ())++smRemoveFile :: FileName -> SlurpMonad ()+smRemoveFile f =+ do exists <- mDoesFileExist f+ if exists then smRemove f+ else fail $ "File "++fn2fp f++" does not exist."++smRemoveDirectory :: FileName -> SlurpMonad ()+smRemoveDirectory f =+ do exists <- mDoesDirectoryExist f+ if exists then smRemove f+ else fail $ "Directory "++fn2fp f++" does not exist."++-- | Here are a few access functions.+slurp_name (Slurpy n _) = fn2fp n+slurp_fn :: Slurpy -> FileName+slurp_fn (Slurpy n _) = n+slurp_setname :: FileName -> Slurpy -> Slurpy+slurp_setname f (Slurpy _ s) = Slurpy f s++is_file (Slurpy _ (SlurpDir _ _)) = False+is_file (Slurpy _ (SlurpFile _ _)) = True++is_dir (Slurpy _ (SlurpDir _ _)) = True+is_dir (Slurpy _ (SlurpFile _ _)) = False++get_filecontents (Slurpy _ (SlurpFile _ c)) = c+get_filecontents _ = bug "Can't get_filecontents on SlurpDir."++get_dircontents (Slurpy _ (SlurpDir _ c)) = map_to_slurpies c+get_dircontents _ = bug "Can't get_dircontents on SlurpFile."++get_mtime (Slurpy _ (SlurpFile (_,t,_) _)) = t+get_mtime _ = bug "can't get_mtime on SlurpDir."+get_length (Slurpy _ (SlurpFile (_,_,l) _)) = l+get_length _ = bug "can't get_length on SlurpDir."++undefined_time :: EpochTime+undefined_time = -1+undefined_size :: FileOffset+undefined_size = -1+undef_time_size :: (Maybe String, EpochTime, FileOffset)+undef_time_size = (Nothing, undefined_time, undefined_size)++wait_a_moment :: IO ()+wait_a_moment = do { sleep 1; return () }+ -- HACKERY: In ghc 6.1, sleep has the type signature IO Int; it+ -- returns an integer just like sleep(3) does. To stay compatible+ -- with older versions, though, we just ignore sleep's return+ -- value. Hackery, like I said.++isFileReallySymlink :: FilePath -> IO Bool+isFileReallySymlink f = do fs <- getSymbolicLinkStatus f+ return (isSymbolicLink fs)++doesFileReallyExist :: FilePath -> IO Bool+doesFileReallyExist f = do fs <- getSymbolicLinkStatus f+ return (isRegularFile fs)++doesDirectoryReallyExist :: FilePath -> IO Bool+doesDirectoryReallyExist f = do fs <- getSymbolicLinkStatus f+ return (isDirectory fs)++-- |slurp is how we get a slurpy in the first place\ldots+slurp = slurp_unboring (\_->True) . toPath+mmap_slurp d = do canmmap <- can_I_use_mmap+ if canmmap then genslurp True (\_->True) d+ else genslurp False (\_->True) d+slurp_unboring = genslurp False+genslurp :: Bool -> (FilePath -> Bool)+ -> FilePath -> IO Slurpy+genslurp usemm nb dirname = do+ isdir <- doesDirectoryExist dirname+ ms <- if isdir+ then withCurrentDirectory dirname $+ do actualname <- getCurrentDirectory+ genslurp_helper usemm nb (reverse actualname) "" "."+ else do former_dir <- getCurrentDirectory+ genslurp_helper usemm nb (reverse former_dir) "" dirname+ case ms of+ Just s -> return s+ Nothing -> fail $ "Unable to read directory " ++ dirname +++ " (it appears to be neither file nor directory)"++unsafeInterleaveMapIO :: (a -> IO b) -> [a] -> IO [b]+unsafeInterleaveMapIO _ [] = return []+unsafeInterleaveMapIO f (x:xs)+ = do x' <- f x+ xs' <- unsafeInterleaveIO $ unsafeInterleaveMapIO f xs+ return (x':xs')++genslurp_helper :: Bool -> (FilePath -> Bool)+ -> FilePath -> String -> String -> IO (Maybe Slurpy)+genslurp_helper usemm nb formerdir fullpath dirname = do+ fs <- getSymbolicLinkStatus fulldirname+ if isRegularFile fs+ then do let mtime = (Nothing, modificationTime fs, fileSize fs)+ ls <- unsafeInterleaveIO $ myReadFileLinesPSetc fulldirname+ return $ Just $ Slurpy (fp2fn dirname) $ SlurpFile mtime ls+ else if isDirectory fs || (isSymbolicLink fs && dirname == ".")+ then do sl <- unsafeInterleaveIO $+ do fnames <- getDirectoryContents fulldirname+ unsafeInterleaveMapIO+ (\f -> genslurp_helper usemm nb fulldirname'+ (fullpath///f) f)+ $ filter (nb . (fullpath///)) $ filter not_hidden fnames+ return $ Just $ Slurpy (fp2fn dirname) $ SlurpDir Nothing $ slurpies_to_map $ catMaybes sl+ else return Nothing+ where fulldirname' = formerdir\\\dirname+ fulldirname = reverse fulldirname'+ myReadFileLinesPSetc = if usemm then mmapFilePS+ else B.readFile++not_hidden :: FilePath -> Bool+not_hidden "." = False+not_hidden ".." = False+not_hidden _ = True++(\\\) :: FilePath -> FilePath -> FilePath+(\\\) "" d = d+(\\\) d "." = d+(\\\) d subdir = reverse subdir ++ "/" ++ d++(///) :: FilePath -> FilePath -> FilePath+(///) "" d = d+(///) d "." = d+(///) d subdir = d ++ "/" ++ subdir++co_slurp :: Slurpy -> FilePath -> IO Slurpy+co_slurp guide dirname = do+ isdir <- doesDirectoryExist dirname+ if isdir+ then withCurrentDirectory dirname $ do+ actualname <- getCurrentDirectory+ Just slurpy <- co_slurp_helper (reverse actualname) guide+ return slurpy+ else error "Error coslurping!!! Please report this."++co_slurp_helper :: FilePath -> Slurpy -> IO (Maybe Slurpy)+co_slurp_helper former_dir (Slurpy d (SlurpDir _ c)) = unsafeInterleaveIO $ do+ let d' = fn2fp d+ fn' = former_dir\\\d'+ fn = reverse fn'+ efs <- tryNonSignal $ getSymbolicLinkStatus fn+ case efs of+ Right fs+ | isDirectory fs || (isSymbolicLink fs && d' == ".") ->+ do sl <- unsafeInterleaveIO+ $ unsafeInterleaveMapIO (co_slurp_helper fn') (map_to_slurpies c)+ return $ Just $ Slurpy d $ SlurpDir Nothing $ slurpies_to_map $ catMaybes sl+ _ -> return Nothing+co_slurp_helper former_dir (Slurpy f (SlurpFile _ _)) = unsafeInterleaveIO $ do+ let fn' = former_dir\\\fn2fp f+ fn = reverse fn'+ efs <- tryNonSignal $ getSymbolicLinkStatus fn+ case efs of+ Right fs+ | isRegularFile fs ->+ do let mtime = (Nothing, modificationTime fs, fileSize fs)+ ls <- unsafeInterleaveIO $ B.readFile fn+ return $ Just $ Slurpy f $ SlurpFile mtime ls+ _ -> return Nothing++get_slurp_context_generic :: (Slurpy -> a) -> (a -> [Slurpy]) -> FileName -> Slurpy -> Maybe (a -> a, Slurpy)+get_slurp_context_generic h1 h2 fn0 s0 =+ let norm_fn0 = norm_path fn0 in+ if norm_fn0 == empty+ then Just (id, s0)+ else slurp_context_private norm_fn0 id s0+ where+ slurp_context_private f ctx s@(Slurpy f' (SlurpFile _ _)) =+ if f == f' then Just (ctx, s)+ else Nothing+ slurp_context_private f ctx s@(Slurpy d (SlurpDir _ c))+ | f == d = Just (ctx, s)+ | d == dot =+ case break_on_dir f of+ Just (dn,fn) | dn == dot ->+ descend fn+ _ ->+ descend f+ | otherwise =+ case break_on_dir f of+ Just (dn,fn) ->+ if dn == d+ then descend fn+ else Nothing+ _ -> Nothing+ where+ descend fname =+ case findSubSlurpy fname c of+ Nothing -> Nothing+ Just this -> slurp_context_private+ fname+ (ctx . h1 . Slurpy d . SlurpDir Nothing . foldr (uncurry Map.insert) (Map.delete (slurp_fn this) c) . map slurpy_to_pair . h2)+ this++ dot = fp2fn "."+ empty = fp2fn ""++-- |get_slurp_context navigates to a specified filename in the given slurpy,+-- and returns the child slurpy at that point together with a update function that can be used+-- to reconstruct the original slurpy from a replacement value for the child slurpy.+get_slurp_context :: FileName -> Slurpy -> Maybe (Slurpy -> Slurpy, Slurpy)+get_slurp_context = get_slurp_context_generic id return++-- |A variant of 'get_slurp_context' that allows for removing the child slurpy+-- altogether by passing in 'Nothing' to the update function.+-- If the child slurpy happened to be at the top level and 'Nothing' was passed in,+-- then the result of the update function will also be 'Nothing', otherwise it will always+-- be a 'Just' value.+get_slurp_context_maybe :: FileName -> Slurpy -> Maybe (Maybe Slurpy -> Maybe Slurpy, Slurpy)+get_slurp_context_maybe = get_slurp_context_generic Just maybeToList++-- |A variant of 'get_slurp_context' that allows for replacing the child slurpy by+-- a list of slurpies. The result of the update function will always be a singleton+-- list unless the child slurpy was at the top level.+-- Currently unused.+-- get_slurp_context_list :: FileName -> Slurpy -> Maybe ([Slurpy] -> [Slurpy], Slurpy)+-- get_slurp_context_list = get_slurp_context_generic return id++-- | It is important to be able to readily modify a slurpy.+slurp_remove :: FileName -> Slurpy -> Maybe Slurpy+slurp_remove fname s@(Slurpy _ (SlurpDir _ _)) =+ case get_slurp_context_maybe fname s of+ Just (ctx, _) -> ctx Nothing+ Nothing -> Nothing+slurp_remove _ _ = bug "slurp_remove only acts on SlurpDirs"++slurp_removefile :: FileName -> Slurpy -> Maybe Slurpy+slurp_removefile f s =+ if slurp_hasfile f s+ then case slurp_remove f s of+ s'@(Just (Slurpy _ (SlurpDir _ _))) -> s'+ _ -> impossible+ else Nothing++slurp_move :: FileName -> FileName -> Slurpy -> Maybe Slurpy+slurp_move f f' s =+ if not (slurp_has (fn2fp f') s) && slurp_hasdir (super_name f') s+ then case get_slurp f s of+ Nothing -> Nothing+ Just sf ->+ case slurp_remove f s of+ Nothing -> Nothing+ Just (s'@(Slurpy _ (SlurpDir _ _))) ->+ Just $ addslurp f' (slurp_setname (own_name f') sf) s'+ _ -> impossible+ else Nothing++addslurp :: FileName -> Slurpy -> Slurpy -> Slurpy+addslurp fname s s' =+ case get_slurp_context (super_name fname) s' of+ Just (ctx, Slurpy d (SlurpDir _ c)) -> ctx (Slurpy d (SlurpDir Nothing (uncurry Map.insert (slurpy_to_pair s) c)))+ _ -> s'++get_slurp :: FileName -> Slurpy -> Maybe Slurpy+get_slurp f s = fmap snd (get_slurp_context f s)++slurp_removedir :: FileName -> Slurpy -> Maybe Slurpy+slurp_removedir f s =+ case get_slurp f s of+ Just (Slurpy _ (SlurpDir _ l)) | Map.null l ->+ case slurp_remove f s of+ s'@(Just (Slurpy _ (SlurpDir _ _))) -> s'+ _ -> impossible+ _ -> Nothing++slurp_adddir :: FileName -> Slurpy -> Maybe Slurpy+slurp_adddir f s =+ if slurp_hasfile f s || slurp_hasdir f s || not (slurp_hasdir (super_name f) s)+ then Nothing+ else Just $ addslurp f (Slurpy (own_name f) (SlurpDir Nothing Map.empty)) s++-- |Code to modify a given file in a slurpy.+slurp_modfile :: FileName -> (FileContents -> Maybe FileContents)+ -> Slurpy -> Maybe Slurpy+slurp_modfile fname modify sl =+ case get_slurp_context fname sl of+ Just (ctx, Slurpy ff (SlurpFile _ c)) ->+ case modify c of+ Nothing -> Nothing+ Just c' -> Just (ctx (Slurpy ff (SlurpFile undef_time_size c')))+ _ -> + Nothing++slurp_hasfile :: FileName -> Slurpy -> Bool+slurp_hasfile f s =+ case get_slurp f s of+ Just s' | is_file s' -> True+ _ -> False++slurp_has :: FilePath -> Slurpy -> Bool+slurp_has f s = isJust (get_slurp (fp2fn f) s)++slurp_has_anycase :: FilePath -> Slurpy -> Bool+slurp_has_anycase fname s =+ seq normed_name $ isJust $ get_slurp normed_name $ mapSlurpyNames tolower s+ where normed_name = norm_path $ fp2fn $ map toLower fname++tolower :: FileName -> FileName+tolower = fp2fn . (map toLower) . fn2fp++findSubSlurpy :: FileName -> SlurpyMap -> Maybe Slurpy+findSubSlurpy fn sm =+ let topname = case break_on_dir fn of+ Just (dn, _) -> dn+ Nothing -> fn+ in fmap (Slurpy topname) (Map.lookup topname sm)++slurp_hasdir :: FileName -> Slurpy -> Bool+slurp_hasdir d _ | norm_path d == fp2fn "" = True+slurp_hasdir f (Slurpy _ (SlurpDir _ c)) =+ seq f $ let f' = norm_path f+ in case findSubSlurpy f' c of+ Just s -> slurp_hasdir_private f' s+ Nothing -> False+slurp_hasdir _ _ = False++slurp_hasdir_private :: FileName -> Slurpy -> Bool+slurp_hasdir_private _ (Slurpy _ (SlurpFile _ _)) = False+slurp_hasdir_private f (Slurpy d (SlurpDir _ c))+ | f == d = True+ | otherwise =+ case break_on_dir f of+ Just (dn,fn) ->+ if dn == d+ then case findSubSlurpy fn c of+ Just s -> slurp_hasdir_private fn s+ Nothing -> False+ else False+ _ -> False++get_path_list :: Slurpy -> FilePath -> [FilePath]+get_path_list s fp = get_path_list' s ("./" ++ fp)++get_path_list' :: Slurpy -> FilePath -> [FilePath]+get_path_list' s "" = list_slurpy s+get_path_list' (Slurpy f (SlurpFile _ _)) fp+ | f' == fp = [f']+ where f' = fn2fp f+get_path_list' (Slurpy d (SlurpDir _ ss)) fp+ | (d' ++ "/") `isPrefixOf` (fp ++ "/")+ = let fp' = drop (length d' + 1) fp+ in map (d' ///) $ concatMap (\s -> get_path_list' s fp') $ map_to_slurpies ss+ where d' = fn2fp d+get_path_list' _ _ = []++list_slurpy :: Slurpy -> [FilePath]+list_slurpy (Slurpy f (SlurpFile _ _)) = [fn2fp f]+list_slurpy (Slurpy dd (SlurpDir _ ss)) = d : map (d ///) (concatMap list_slurpy (map_to_slurpies ss))+ where d = fn2fp dd++list_slurpy_files :: Slurpy -> [FilePath]+list_slurpy_files (Slurpy f (SlurpFile _ _)) = [fn2fp f]+list_slurpy_files (Slurpy dd (SlurpDir _ ss)) =+ map ((fn2fp dd) ///) (concatMap list_slurpy_files (map_to_slurpies ss))++list_slurpy_dirs :: Slurpy -> [FilePath]+list_slurpy_dirs (Slurpy _ (SlurpFile _ _)) = []+list_slurpy_dirs (Slurpy dd (SlurpDir _ ss)) =+ d : map (d ///) (concatMap list_slurpy_dirs (map_to_slurpies ss))+ where d = fn2fp dd++unsyncedSlurpySize :: Slurpy -> Int+unsyncedSlurpySize (Slurpy _ (SlurpFile (_,_,size) ps))+ | size == undefined_size = B.length ps+ | otherwise = 0+unsyncedSlurpySize (Slurpy _ (SlurpDir _ ss)) =+ sum $ map unsyncedSlurpySize (map_to_slurpies ss)++slurp_sync_size :: Int+slurp_sync_size = 100 * 1000000++syncSlurpy :: (Slurpy -> IO Slurpy) -> Slurpy -> IO Slurpy+syncSlurpy put s = if unsyncedSlurpySize s > slurp_sync_size+ then put s+ else return s
@@ -0,0 +1,119 @@+% Copyright (C) 2002-2005 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\begin{code}+module Darcs.Test ( get_test,+ run_posthook, run_prehook )+where+import Darcs.RepoPath ( AbsolutePath )+import Darcs.Utils ( withCurrentDirectory )+import System.Exit ( ExitCode(..) )+import System.Cmd ( system )+import Control.Monad ( when )++import Darcs.Arguments ( DarcsFlag( Quiet,+ AskPosthook, AskPrehook ),+ get_posthook_cmd, get_prehook_cmd )+import Darcs.Repository.Prefs ( get_prefval )+import Darcs.Utils ( askUser )+import System.IO ( hPutStrLn, stderr )+\end{code}++If you like, you can configure your repository to be able to run a test+suite of some sort. You can do this by using ``setpref'' to set the+``test'' value to be a command to run, e.g.+\begin{verbatim}+% darcs setpref test "sh configure && make && make test"+\end{verbatim}+Or, if you want to define a test specific to one copy of the repository,+you could do this by editing the file \verb!_darcs/prefs/prefs!.++\begin{options}+--leave-test-directory, --remove-test-directory+\end{options}++Normally darcs deletes the directory in which the test was run afterwards.+Sometimes (especially when the test fails) you'd prefer to be able to be+able to examine the test directory after the test is run. You can do this+by specifying the \verb!--leave-test-directory! flag. Alas, there is no+way to make darcs leave the test directory only if the test fails. The+opposite of \verb!--leave-test-directory! is+\verb!--remove-test-directory!, which could come in handy if you choose to+make \verb!--leave-test-directory! the default (see+section~\ref{defaults}).++\begin{code}+get_test :: [DarcsFlag] -> IO (IO ExitCode)+get_test opts =+ let putInfo s = when (not $ Quiet `elem` opts) $ putStr s+ in do+ testline <- get_prefval "test"+ return $+ case testline of+ Nothing -> return ExitSuccess+ Just testcode -> do+ putInfo "Running test...\n"+ ec <- system testcode+ if ec == ExitSuccess+ then putInfo "Test ran successfully.\n"+ else putInfo "Test failed!\n"+ return ec++run_posthook :: [DarcsFlag] -> AbsolutePath -> IO ExitCode+run_posthook opts repodir = do ph <- get_posthook opts+ withCurrentDirectory repodir $ run_hook opts "Posthook" ph++get_posthook :: [DarcsFlag] -> IO (Maybe String)+get_posthook opts = case get_posthook_cmd opts of+ Nothing -> return Nothing+ Just command ->+ if AskPosthook `elem` opts+ then do yorn <- askUser ("\nThe following command is set to execute.\n"+++ "Execute the following command now (yes or no)?\n"+++ command++"\n")+ case yorn of ('y':_) -> return $ Just command+ _ -> do putStrLn "Posthook cancelled..."+ return Nothing+ else return $ Just command++run_prehook :: [DarcsFlag] -> AbsolutePath -> IO ExitCode+run_prehook opts repodir = do ph <- get_prehook opts+ withCurrentDirectory repodir $ run_hook opts "Prehook" ph++get_prehook :: [DarcsFlag] -> IO (Maybe String)+get_prehook opts = case get_prehook_cmd opts of+ Nothing -> return Nothing+ Just command ->+ if AskPrehook `elem` opts+ then do yorn <- askUser ("\nThe following command is set to execute.\n"+++ "Execute the following command now (yes or no)?\n"+++ command++"\n")+ case yorn of ('y':_) -> return $ Just command+ _ -> do putStrLn "Prehook cancelled..."+ return Nothing+ else return $ Just command++run_hook :: [DarcsFlag] -> String -> Maybe String -> IO ExitCode+run_hook _ _ Nothing = return ExitSuccess+run_hook opts cname (Just command) =+ do ec <- system command+ when (Quiet `notElem` opts) $+ if ec == ExitSuccess+ then putStrLn $ cname++" ran successfully."+ else hPutStrLn stderr $ cname++" failed!"+ return ec+\end{code}
@@ -0,0 +1,106 @@+-- Copyright (C) 2003 David Roundy+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.++module Darcs.TheCommands ( command_control_list ) where++import Darcs.Commands.Add ( add )+import Darcs.Commands.AmendRecord ( amendrecord )+import Darcs.Commands.Annotate ( annotate )+import Darcs.Commands.Apply ( apply )+import Darcs.Commands.Changes ( changes )+import Darcs.Commands.Check ( check )+import Darcs.Commands.Convert ( convert )+import Darcs.Commands.Diff+import Darcs.Commands.Dist ( dist )+import Darcs.Commands.Get ( get, clone )+#ifdef HAVE_HASKELL_ZLIB+import Darcs.Commands.GZCRCs ( gzcrcs )+#else+-- import just to check it compiles+import Darcs.Commands.GZCRCs ()+#endif+import Darcs.Commands.Init ( initialize )+import Darcs.Commands.Show ( show_command, list, query )+import Darcs.Commands.MarkConflicts ( markconflicts, resolve )+import Darcs.Commands.Move ( move, mv )+import Darcs.Commands.Optimize ( optimize )+import Darcs.Commands.Pull ( pull )+import Darcs.Commands.Push ( push )+import Darcs.Commands.Put ( put )+import Darcs.Commands.Record ( record, commit )+import Darcs.Commands.Remove ( remove, rm, unadd )+import Darcs.Commands.Repair ( repair )+import Darcs.Commands.Replace ( replace )+import Darcs.Commands.Revert ( revert )+import Darcs.Commands.Rollback ( rollback )+import Darcs.Commands.Send ( send )+import Darcs.Commands.SetPref ( setpref )+import Darcs.Commands.Tag ( tag )+import Darcs.Commands.TrackDown ( trackdown )+import Darcs.Commands.TransferMode ( transfer_mode )+import Darcs.Commands.Unrecord ( unrecord, unpull, obliterate )+import Darcs.Commands.Unrevert ( unrevert )+import Darcs.Commands.WhatsNew ( whatsnew )+import Darcs.Commands ( CommandControl(Command_data,Hidden_command,Group_name) )++-- | The commands that darcs knows about (e.g. whatsnew, record),+-- organized into thematic groups. Note that hidden commands+-- are also listed here.+command_control_list :: [CommandControl]+command_control_list = [Group_name "Changing and querying the working copy:",+ Command_data add,+ Command_data remove, Hidden_command unadd, Hidden_command rm,+ Command_data move, Hidden_command mv,+ Command_data replace,+ Command_data revert,+ Command_data unrevert,+ Command_data whatsnew,+ Group_name "Copying changes between the working copy and the repository:",+ Command_data record, Hidden_command commit,+ Command_data unrecord,+ Command_data amendrecord,+ Command_data markconflicts, Hidden_command resolve,+ Group_name "Direct modification of the repository:",+ Command_data tag,+ Command_data setpref,+ Group_name "Querying the repository:",+ Command_data diff_command,+ Command_data changes,+ Command_data annotate,+ Command_data dist,+ Command_data trackdown,+ Command_data show_command, Hidden_command list, Hidden_command query,+ Hidden_command transfer_mode,+ Group_name "Copying patches between repositories with working copy update:",+ Command_data pull,+ Command_data obliterate, Hidden_command unpull,+ Command_data rollback,+ Command_data push,+ Command_data send,+ Command_data apply,+ Command_data get, Hidden_command clone,+ Command_data put,+ Group_name "Administrating repositories:",+ Command_data initialize,+ Command_data optimize,+ Command_data check,+ Command_data repair,+ Command_data convert+#ifdef HAVE_HASKELL_ZLIB+ ,Hidden_command gzcrcs+#endif+ ]
@@ -0,0 +1,81 @@+{-# OPTIONS_GHC -cpp #-}+{-# LANGUAGE CPP #-}++{-+Copyright (C) 2004 David Roundy++This program is free software; you can redistribute it and/or modify+it under the terms of the GNU General Public License as published by+the Free Software Foundation; either version 2, or (at your option)+any later version.++This program is distributed in the hope that it will be useful,+but WITHOUT ANY WARRANTY; without even the implied warranty of+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+GNU General Public License for more details.++You should have received a copy of the GNU General Public License+along with this program; see the file COPYING. If not, write to+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+Boston, MA 02110-1301, USA.+-}++{-|++ Path resolving:++ * A URL contains the sequence @\":\/\/\"@.++ * A local filepath does not contain colons, except+ as second character (windows drives).++ * A path that is neither a URL nor a local file+ is an ssh-path.++ Examples:++ > /usr/repo/foo -- local file+ > c:/src/darcs -- local file+ > http://darcs.net/ -- URL+ > peter@host:/path -- ssh+ > droundy@host: -- ssh+ > host:/path -- ssh++ This means that single-letter hosts in ssh-paths do not work,+ unless a username is provided.++ Perhaps ssh-paths should use @\"ssh:\/\/user\@host\/path\"@-syntax instead?+-}++module Darcs.URL (+ is_file, is_url, is_ssh, is_relative, is_absolute,+ is_ssh_nopath+ ) where++#include "impossible.h"++is_relative :: String -> Bool+is_relative (_:':':_) = False+is_relative f@(c:_) = is_file f && c /= '/' && c /= '~'+is_relative "" = bug "Empty filename in is_relative"++is_absolute :: String -> Bool+is_absolute "" = bug "is_absolute called with empty filename"+is_absolute f = is_file f && (not $ is_relative f)++is_file :: String -> Bool+is_file (_:_:fou) = ':' `notElem` fou+is_file _ = True++is_url :: String -> Bool+is_url (':':'/':'/':_:_) = True+is_url (_:x) = is_url x+is_url "" = False++is_ssh :: String -> Bool+is_ssh s = not (is_file s || is_url s)++is_ssh_nopath :: String -> Bool+is_ssh_nopath s = case reverse s of+ ':':x@(_:_:_) -> ':' `notElem` x+ _ -> False
@@ -0,0 +1,258 @@+-- Various utility functions that do not belong anywhere else.++{-# LANGUAGE CPP, ForeignFunctionInterface #-}++module Darcs.Utils ( catchall, ortryrunning, nubsort, breakCommand,+ clarify_errors, prettyException, prettyError,+ putStrLnError, putDocLnError,+ withCurrentDirectory,+ withUMask, askUser, stripCr,+ showHexLen, add_to_error_loc,+ maybeGetEnv, firstNotBlank, firstJustM, firstJustIO,+ isUnsupportedOperationError, isHardwareFaultError,+ get_viewer, edit_file, promptYorn, promptCharFancy,+ environmentHelpEditor, environmentHelpPager,+ formatPath ) where++import Prelude hiding ( catch )+import Control.Exception ( bracket, bracket_, catch, Exception(IOException), try )+import GHC.IOBase ( IOException(ioe_location),+ IOErrorType(UnsupportedOperation, HardwareFault) )+import System.IO.Error ( isUserError, ioeGetErrorType, ioeGetErrorString )++import Darcs.SignalHandler ( catchNonSignal )+import Numeric ( showHex )+import System.Directory ( doesFileExist )+import System.Exit ( exitWith, ExitCode(..) )+import System.Environment ( getEnv )+import System.IO ( hPutStrLn, stderr )+import Data.Char ( toUpper )+import Darcs.RepoPath ( FilePathLike, getCurrentDirectory, setCurrentDirectory, toFilePath )+import Data.Maybe ( listToMaybe, isJust )+import Data.List ( group, sort )+import Control.Monad ( when )+import Exec ( exec_interactive )+import Printer ( Doc, hPutDocLn )+import Foreign.C.String ( CString, withCString )+import Foreign.C.Error ( throwErrno )+import Foreign.C.Types ( CInt )++import Progress ( withoutProgress )++import System.Console.Haskeline ( runInputT, defaultSettings, getInputLine,+ getInputChar, outputStrLn )+import System.Console.Haskeline.Encoding ( encode )+import qualified Data.ByteString as B ( readFile )+import qualified Data.ByteString.Char8 as B ( unpack )++showHexLen :: (Integral a) => Int -> a -> String+showHexLen n x = let s = showHex x ""+ in replicate (n - length s) ' ' ++ s++add_to_error_loc :: Exception -> String -> Exception+add_to_error_loc (IOException ioe) s+ = IOException $ ioe { ioe_location = s ++ ": " ++ ioe_location ioe }+add_to_error_loc e _ = e++isUnsupportedOperationError :: IOError -> Bool+isUnsupportedOperationError = isUnsupportedOperationErrorType . ioeGetErrorType++isUnsupportedOperationErrorType :: IOErrorType -> Bool+isUnsupportedOperationErrorType UnsupportedOperation = True+isUnsupportedOperationErrorType _ = False++isHardwareFaultError :: IOError -> Bool+isHardwareFaultError = isHardwareFaultErrorType . ioeGetErrorType++isHardwareFaultErrorType :: IOErrorType -> Bool+isHardwareFaultErrorType HardwareFault = True+isHardwareFaultErrorType _ = False++catchall :: IO a -> IO a -> IO a+a `catchall` b = a `catchNonSignal` (\_ -> b)++maybeGetEnv :: String -> IO (Maybe String)+maybeGetEnv s = (getEnv s >>= return.Just) `catchall` return Nothing -- err can only be isDoesNotExist+++-- |The firstJustM returns the first Just entry in a list of monadic operations. This is close to+-- `listToMaybe `fmap` sequence`, but the sequence operator evaluates all monadic members of the+-- list before passing it along (i.e. sequence is strict). The firstJustM is lazy in that list+-- member monads are only evaluated up to the point where the first Just entry is obtained.+firstJustM :: Monad m => [m (Maybe a)] -> m (Maybe a)+firstJustM [] = return Nothing+firstJustM (e:es) = e >>= (\v -> if isJust v then return v else firstJustM es)++-- |The firstJustIO is a slight modification to firstJustM: the+-- entries in the list must be IO monad operations and the+-- firstJustIO will silently turn any monad call that throws an+-- exception into Nothing, basically causing it to be ignored.+firstJustIO :: [IO (Maybe a)] -> IO (Maybe a)+firstJustIO = firstJustM . map (\o -> o `catchall` return Nothing)+++clarify_errors :: IO a -> String -> IO a+clarify_errors a e = a `catch` (\x -> fail $ unlines [prettyException x,e])++prettyException :: Control.Exception.Exception -> String+prettyException (IOException e) | isUserError e = ioeGetErrorString e+prettyException e = show e++prettyError :: IOError -> String+prettyError e | isUserError e = ioeGetErrorString e+ | otherwise = show e++-- | Given two shell commands as arguments, execute the former. The+-- latter is then executed if the former failed because the executable+-- wasn't found (code 127), wasn't executable (code 126) or some other+-- exception occurred. Other failures (such as the user holding ^C)+-- do not cause the second command to be tried.+ortryrunning :: IO ExitCode -> IO ExitCode -> IO ExitCode+a `ortryrunning` b = do+ ret <- try a+ case ret of+ (Right (ExitFailure 126)) -> b -- command not executable+ (Right (ExitFailure 127)) -> b -- command not found+ (Right x) -> return x -- legitimate success/failure+ (Left _) -> b -- an exception++putStrLnError :: String -> IO ()+putStrLnError = hPutStrLn stderr++putDocLnError :: Doc -> IO ()+putDocLnError = hPutDocLn stderr++withCurrentDirectory :: FilePathLike p => p -> IO a -> IO a+withCurrentDirectory name m =+ bracket+ (do cwd <- getCurrentDirectory+ when (toFilePath name /= "") (setCurrentDirectory name)+ return cwd)+ (\oldwd -> setCurrentDirectory oldwd `catchall` return ())+ (const m)++foreign import ccall unsafe "umask.h set_umask" set_umask+ :: CString -> IO CInt+foreign import ccall unsafe "umask.h reset_umask" reset_umask+ :: CInt -> IO CInt++withUMask :: String -> IO a -> IO a+withUMask umask job =+ do rc <-withCString umask set_umask+ when (rc < 0) (throwErrno "Couldn't set umask")+ bracket_+ (return ())+ (reset_umask rc)+ job++askUser :: String -> IO String+askUser prompt = withoutProgress $ runInputT defaultSettings $+ getInputLine prompt+ >>= maybe (error "askUser: unexpected end of input") return+ -- Return the input as encoded, 8-bit Chars (same as the+ -- non-Haskeline backend).+ >>= fmap B.unpack . encode++stripCr :: String -> String+stripCr "" = ""+stripCr "\r" = ""+stripCr (c:cs) = c : stripCr cs++-- |Returns Just l where l is first non-blank string in input array; Nothing if no non-blank entries+firstNotBlank :: [String] -> Maybe String+firstNotBlank = listToMaybe . filter (not . null)+++-- Format a path for screen output,+-- so that the user sees where the path begins and ends.+-- Could (should?) also warn about unprintable characters here.+formatPath :: String -> String+formatPath path = "\"" ++ quote path ++ "\""+ where quote "" = ""+ quote (c:cs) = if c=='\\' || c=='"'+ then '\\':c:quote cs+ else c:quote cs++breakCommand :: String -> (String, [String])+breakCommand s = case words s of+ (arg0:args) -> (arg0,args)+ [] -> (s,[])++nubsort :: Ord a => [a] -> [a]+nubsort = map head . group . sort+++edit_file :: FilePathLike p => p -> IO ExitCode+edit_file ff = do+ ed <- get_editor+ old_content <- file_content+ ec <- exec_interactive ed f+ `ortryrunning` exec_interactive "emacs" f+ `ortryrunning` exec_interactive "emacs -nw" f+ `ortryrunning` exec_interactive "nano" f+#ifdef WIN32+ `ortryrunning` exec_interactive "edit" f+#endif+ new_content <- file_content+ when (new_content == old_content) $ do+ yorn <- promptYorn "File content did not change. Continue anyway?"+ when (yorn == 'n') $ do putStrLn "Aborted."+ exitWith ExitSuccess+ return ec+ where f = toFilePath ff+ file_content = do+ exists <- doesFileExist f+ if exists then do content <- B.readFile f+ return $ Just content+ else return Nothing++get_editor :: IO String+get_editor = getEnv "DARCS_EDITOR" `catchall`+ getEnv "DARCSEDITOR" `catchall`+ getEnv "VISUAL" `catchall`+ getEnv "EDITOR" `catchall` return "vi"++environmentHelpEditor :: ([String], [String])+environmentHelpEditor = (["DARCS_EDITOR", "DARCSEDITOR", "VISUAL", "EDITOR"],[+ "To edit a patch description of email comment, Darcs will invoke an",+ "external editor. Your preferred editor can be set as any of the",+ "environment variables $DARCS_EDITOR, $DARCSEDITOR, $VISUAL or $EDITOR.",+ "If none of these are set, vi(1) is used. If vi crashes or is not",+ "found in your PATH, emacs, emacs -nw, nano and (on Windows) edit are",+ "each tried in turn."])++get_viewer :: IO String+get_viewer = getEnv "DARCS_PAGER" `catchall`+ getEnv "PAGER" `catchall` return "less"++environmentHelpPager :: ([String], [String])+environmentHelpPager = (["DARCS_PAGER", "PAGER"],[+ "Darcs will sometimes invoke a pager if it deems output to be too long",+ "to fit onscreen. Darcs will use the pager specified by $DARCS_PAGER",+ "or $PAGER. If neither are set, `less' will be used."])++promptYorn :: [Char] -> IO Char+promptYorn p = promptCharFancy p "yn" Nothing []++promptCharFancy :: String -> [Char] -> Maybe Char -> [Char] -> IO Char+promptCharFancy p chs md help_chs = withoutProgress $ runInputT defaultSettings $+ loopChar+ where+ loopChar = do+ let prompt = p ++ " [" ++ setDefault chs ++ "]" ++ helpStr+ a <- getInputChar prompt >>= maybe (error "promptCharFancy: unexpected end of input")+ return+ case () of + _ | a `elem` chs -> return a+ | a == ' ' -> case md of Nothing -> tryAgain + Just d -> return d+ | a `elem` help_chs -> return a+ | otherwise -> tryAgain+ helpStr = case help_chs of+ [] -> ""+ (h:_) -> ", or " ++ (h:" for help: ")+ tryAgain = do outputStrLn "Invalid response, try again!"+ loopChar+ setDefault s = case md of Nothing -> s+ Just d -> map (setUpper d) s+ setUpper d c = if d == c then toUpper c else c
@@ -0,0 +1,168 @@+-- Copyright (C) 2004 David Roundy+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.+++{-# OPTIONS_GHC -fglasgow-exts #-}+{-# LANGUAGE ExistentialQuantification #-}++module DateMatcher ( parseDateMatcher+ -- for debugging only+ , DateMatcher(..), getMatchers ) where++import Control.Exception ( catchJust, userErrors )+import Data.Maybe ( isJust )+import System.Time+import IsoDate ( parseDate, englishDateTime, englishInterval, englishLast, iso8601_interval,+ resetCalendar, subtractFromMCal, getLocalTz,+ MCalendarTime(..), toMCalendarTime, unsafeToCalendarTime,+ unsetTime,+ )+import Text.ParserCombinators.Parsec ( eof, parse, ParseError )++-- | 'withinDay' @x y@ is true if @x <= y < (x + one_day)@+-- Note that this converts the two dates to @ClockTime@ to avoid+-- any timezone-related errors+withinDay :: CalendarTime -> CalendarTime -> Bool+withinDay a b = within (Just $ toClockTime a)+ (Just (addToClockTime day $ toClockTime a))+ (toClockTime b)+ where day = TimeDiff 0 0 1 0 0 0 0++-- | 'dateRange' @x1 x2 y@ is true if @x1 <= y < x2@+-- Since @x1@ and @x2@ can be underspecified, we simply assume the+-- first date that they could stand for.+dateRange :: Maybe MCalendarTime -> Maybe MCalendarTime -> CalendarTime -> Bool+dateRange a b c = cDateRange (fmap unsafeToCalendarTime a)+ (fmap unsafeToCalendarTime b) c++-- | 'cDateRange' @x1 x2 y@ is true if @x1 <= y < x2@+cDateRange :: Maybe CalendarTime -> Maybe CalendarTime -> CalendarTime -> Bool+cDateRange a b c = within (fmap toClockTime a)+ (fmap toClockTime b) (toClockTime c)++-- | 'within' @x1 x2 y@ is true if @x1 <= y < x2@+within :: Maybe ClockTime -> Maybe ClockTime -> ClockTime -> Bool+within (Just start) (Just end) time = start <= time && time < end+within Nothing (Just end) time = time < end+within (Just start) Nothing time = start <= time+within _ _ _ = undefined++-- | 'samePartialDate' @range exact@ is true if @exact@ falls+-- within the a range of dates represented by @range@.+-- The purpose of this function is to support matching on partially+-- specified dates. That is, if you only specify the date 2007,+-- this function should match any dates within that year. On the+-- other hand, if you specify 2007-01, this function will match any+-- dates within that month. This function only matches up to the+-- second.+samePartialDate :: MCalendarTime -> CalendarTime -> Bool+samePartialDate a b_ =+ within (Just clockA)+ (Just $ addToClockTime interval clockA)+ (toClockTime calB)+ where interval+ | isJust (mctSec a) = second+ | isJust (mctMin a) = minute+ | isJust (mctHour a) = hour+ | isJust (mctYDay a) = day+ | mctWeek a = maybe week (const day) (mctWDay a)+ | isJust (mctDay a) = day+ | isJust (mctMonth a) = month+ | otherwise = year+ year = TimeDiff 1 0 0 0 0 0 0+ month = TimeDiff 0 1 0 0 0 0 0+ week = TimeDiff 0 0 7 0 0 0 0+ day = TimeDiff 0 0 1 0 0 0 0+ hour = TimeDiff 0 0 0 1 0 0 0+ minute = TimeDiff 0 0 0 0 1 0 0+ second = TimeDiff 0 0 0 0 0 1 0+ --+ clockA = toClockTime $ unsafeToCalendarTime a+ calB = resetCalendar b_++-- | A 'DateMatcher' combines a potential parse for a date string+-- with a "matcher" function that operates on a given date.+-- We use an existential type on the matcher to allow+-- the date string to either be interpreted as a point in time+-- or as an interval.+data DateMatcher = forall d . (Show d) =>+ DM String -- name+ (Either ParseError d) -- parser+ (d -> CalendarTime -> Bool) -- matcher++-- | 'parseDateMatcher' @s@ return the first matcher in+-- 'getMatchers' that can parse 's'+parseDateMatcher :: String -> IO (CalendarTime -> Bool)+parseDateMatcher d = + do matcher <- tryMatchers `fmap` getMatchers d+ -- Hack: test the matcher against the current date and discard the results.+ -- We just want to make sure it won't throw any exceptions when we use it for real.+ matcher `fmap` now >>= (`seq` return matcher)+ `catchUserError`+ -- If the user enters a date > maxint seconds ago, the toClockTime+ -- function cannot work.+ \e -> if e == "Time.toClockTime: invalid input"+ then error "Can't handle dates that far back!"+ else error e+ where+ catchUserError = catchJust userErrors++-- | 'getMatchers' @d@ returns the list of matchers that will be+-- applied on @d@. If you wish to extend the date parsing code,+-- this will likely be the function that you modify to do so.+getMatchers :: String -> IO [DateMatcher]+getMatchers d =+ do rightNow <- now+ let midnightToday = unsetTime rightNow+ mRightNow = toMCalendarTime rightNow+ matchIsoInterval (Left dur) = dateRange (Just $ dur `subtractFromMCal` mRightNow) (Just mRightNow)+ matchIsoInterval (Right (a,b)) = dateRange (Just a) (Just b)+ tzNow <- getLocalTz+ return -- note that the order of these is quite important as some matchers+ -- can match the same date.+ [ DM "from English date"+ (parseDateWith $ englishLast midnightToday)+ (\(a,_) -> cDateRange (Just a) Nothing)+ , DM "specific English date"+ (parseDateWith $ englishDateTime midnightToday)+ withinDay+ , DM "English interval"+ (parseDateWith $ englishInterval rightNow)+ (uncurry cDateRange)+ , DM "ISO 8601 interval"+ (parseDateWith $ iso8601_interval tzNow)+ matchIsoInterval+ , DM "CVS, ISO 8601, or old style date"+ (parseDate tzNow d)+ samePartialDate ]+ where+ tillEof p = do { x <- p; eof; return x }+ parseDateWith p = parse (tillEof p) "" d++-- | 'tryMatchers' @ms@ returns the first successful match in @ms@+-- It is an error if there are no matches+tryMatchers :: [DateMatcher] -> (CalendarTime -> Bool)+tryMatchers (DM _ parsed matcher : ms) =+ case parsed of+ Left _ -> tryMatchers ms+ Right d -> matcher d+tryMatchers [] = error "Can't support fancy dates."++-- darcs-doc: self-explanatory+now :: IO CalendarTime+now = getClockTime >>= toCalendarTime+
@@ -0,0 +1,76 @@+-- Copyright (C) 2008 Eric Kow+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.++-- | This modules provides rudimentary natural language generation+-- (NLG) utilities. That is, generating natural language from a+-- machine representation. Initially, only English is supported at+-- all. Representations are implemented for:+--+-- * countable nouns (plurality); and+-- * lists of clauses (foo, bar and/or baz).+module English where++import Data.List (isSuffixOf, intersperse)++-- | > englishNum 0 (Noun "watch") "" == "watches"+-- > englishNum 1 (Noun "watch") "" == "watch"+-- > englishNum 2 (Noun "watch") "" == "watches"+englishNum :: Countable n => Int -> n -> ShowS+englishNum x = if x == 1 then singular else plural++-- | Things that have a plural and singular spelling+class Countable a where+ plural :: a -> ShowS+ singular :: a -> ShowS++-- | This only distinguishes between nouns with a final -ch,+-- and nouns which do not.+-- More irregular nouns will just need to have their own type+--+-- > plural (Noun "batch") "" == "batches"+-- > plural (Noun "bat") "" == "bats"+-- > plural (Noun "mouse") "" == "mouses" -- :-(+newtype Noun = Noun String++instance Countable Noun where+ -- more irregular nouns will just need to have their own type+ plural (Noun s) | "ch" `isSuffixOf` s = showString s . showString "es"+ plural (Noun s) = showString s . showChar 's'+ singular (Noun s) = showString s++-- | > singular This (Noun "batch") "" == "this batch"+-- > plural This (Noun "batch") "" == "these batches"+data This = This Noun++instance Countable This where+ plural (This s) = showString "these " . plural s+ singular (This s) = showString "this " . singular s++-- | Given a list of things, combine them thusly:+--+-- > orClauses ["foo", "bar", "baz"] == "foo, bar or baz"+andClauses, orClauses :: [String] -> String+andClauses = intersperseLast ", " " and "+orClauses = intersperseLast ", " " or "++-- | As 'intersperse', with a different separator for the last+-- | interspersal.+intersperseLast :: String -> String -> [String] -> String+intersperseLast _ _ [] = ""+intersperseLast _ _ [clause] = clause+intersperseLast sep sepLast clauses =+ concat (intersperse sep $ init clauses) ++ sepLast ++ last clauses
@@ -0,0 +1,180 @@+-- Copyright (C) 2003 David Roundy+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.+++{-# OPTIONS_GHC -fglasgow-exts #-}+{-# LANGUAGE CPP, ForeignFunctionInterface #-}+-- , DeriveDataTypeable #-}++module Exec ( exec, exec_interactive,+ withoutNonBlock,+ Redirects, Redirect(..),+ ExecException(..)+ ) where++import Data.Typeable ( Typeable )++#ifndef WIN32+import Control.Exception ( bracket )+import System.Posix.Env ( setEnv, getEnv, unsetEnv )+import System.Posix.IO ( queryFdOption, setFdOption, FdOption(..), stdInput )+import System.IO ( stdin )+#else+import Control.Exception ( catchJust, Exception(IOException) )+import Data.List ( isInfixOf )+#endif++import System.Exit ( ExitCode (..) )+import System.Cmd ( system )+import System.IO ( IOMode(..), openBinaryFile, stdout )+import System.Process ( runProcess, terminateProcess, waitForProcess )+import GHC.Handle ( hDuplicate )+ -- urgh. hDuplicate isn't available from a standard place.+import Control.Exception ( bracketOnError )++import Darcs.Global ( whenDebugMode )+import Progress ( withoutProgress )++{-+ A redirection is a three-tuple of values (in, out, err).+ The most common values are:++ AsIs don't change it+ Null /dev/null on Unix, NUL on Windows+ File open a file for reading or writing++ There is also the value Stdout, which is only meaningful for+ redirection of errors, and is performed AFTER stdout is+ redirected so that output and errors mix together. StdIn and+ StdErr could be added as well if they are useful.++ NOTE: Lots of care must be taken when redirecting stdin, stdout+ and stderr to one of EACH OTHER, since the ORDER in which they+ are changed have a significant effect on the result.+-}++type Redirects = (Redirect, Redirect, Redirect)+data Redirect = AsIs | Null | File FilePath+ | Stdout+ deriving Show++{-+ ExecException is thrown by exec if any system call fails,+ for example because the executable we're trying to run+ doesn't exist.+-}+-- ExecException cmd args redirecs errorDesc+data ExecException = ExecException String [String] Redirects String+ deriving (Typeable,Show)+++_dev_null :: FilePath+#ifdef WIN32+_dev_null = "NUL"+#else+_dev_null = "/dev/null"+#endif++{-+ We use System.Process, which does the necessary quoting+ and redirection for us behind the scenes.+-}++exec :: String -> [String] -> Redirects -> IO ExitCode+exec cmd args (inp,out,err) = withoutProgress $ do+ h_stdin <- redirect inp ReadMode+ h_stdout <- redirect out WriteMode+ h_stderr <- redirect err WriteMode+-- putStrLn (unwords (cmd:args ++ map show [inp,out,err]))+ withExit127 $ bracketOnError+ (do whenDebugMode $ putStrLn $ unwords $ cmd:args ++ ["; #"] ++ map show [inp,out,err]+ runProcess cmd args Nothing Nothing h_stdin h_stdout h_stderr)+ (terminateProcess)+ (waitForProcess)+ where+ redirect AsIs _ = return Nothing+ redirect Null mode = Just `fmap` openBinaryFile _dev_null mode+ redirect (File "/dev/null") mode = redirect Null mode+ redirect (File f) mode = Just `fmap` openBinaryFile f mode+ redirect Stdout _ = Just `fmap` hDuplicate stdout+ -- hDuplicate stdout rather than passing stdout itself,+ -- because runProcess closes the Handles we pass it.++exec_interactive :: String -> String -> IO ExitCode++#ifndef WIN32+{-+This should handle arbitrary commands interpreted by the shell on Unix since+that's what people expect. But we don't want to allow the shell to interpret+the argument in any way, so we set an environment variable and call+cmd "$DARCS_ARGUMENT"+-}+exec_interactive cmd arg = withoutProgress $ do+ let var = "DARCS_ARGUMENT"+ stdin `seq` return ()+ withoutNonBlock $ bracket+ (do oldval <- getEnv var+ setEnv var arg True+ return oldval)+ (\oldval ->+ do case oldval of+ Nothing -> unsetEnv var+ Just val -> setEnv var val True)+ (\_ -> withExit127 $ system $ cmd++" \"$"++var++"\"")++#else++exec_interactive cmd arg = withoutProgress $ do+ system $ cmd ++ " " ++ arg+#endif++withoutNonBlock :: IO a -> IO a++#ifndef WIN32+{-+Do IO without NonBlockingRead on stdInput.++This is needed when running unsuspecting external commands with interactive+mode - if read from terminal is non-blocking also write to terminal is+non-blocking.+-}+withoutNonBlock x =+ do nb <- queryFdOption stdInput NonBlockingRead+ if nb+ then bracket+ (do setFdOption stdInput NonBlockingRead False)+ (\_ -> setFdOption stdInput NonBlockingRead True)+ (\_ -> x)+ else do x+#else+withoutNonBlock x = do x+#endif++{-+Ensure that we exit 127 if the thing we are trying to run does not exist+(Only needed under Windows)+-}+withExit127 :: IO ExitCode -> IO ExitCode+#ifdef WIN32+withExit127 a = catchJust notFoundError a (const $ return $ ExitFailure 127)++notFoundError :: Exception -> Maybe ()+notFoundError (IOException e) | "runProcess: does not exist" `isInfixOf` show e = Just ()+notFoundError _ = Nothing+#else+withExit127 = id+#endif
@@ -0,0 +1,107 @@+{-# OPTIONS_GHC -cpp #-}+{-# LANGUAGE CPP #-}++module HTTP( fetchUrl, postUrl, request_url, wait_next_url ) where++import Darcs.Global ( debugFail )++#ifdef HAVE_HTTP+import Control.Monad ( when )+import Data.IORef ( newIORef, readIORef, writeIORef, IORef )+import Network.HTTP+import Network.URI+import System.Environment ( getEnv )+import System.IO.Error ( ioeGetErrorString )+import System.IO.Unsafe ( unsafePerformIO )+import Darcs.Global ( debugMessage )+import qualified Data.ByteString as B+import qualified Data.ByteString.Char8 as BC+#endif++fetchUrl :: String -> IO String+postUrl+ :: String -- ^ url+ -> String -- ^ body+ -> String -- ^ mime type+ -> IO () -- ^ result++request_url :: String -> FilePath -> a -> IO String+wait_next_url :: IO (String, String)++#ifdef HAVE_HTTP++headers :: [Header]+headers = [Header HdrUserAgent $ "darcs-HTTP/" ++ PACKAGE_VERSION]++fetchUrl url = case parseURI url of+ Nothing -> fail $ "Invalid URI: " ++ url+ Just uri -> do debugMessage $ "Fetching over HTTP: "++url+ proxy <- getProxy+ when (not $ null proxy) $+ debugFail "No proxy support for HTTP package yet (try libcurl)!"+ resp <- simpleHTTP $ Request { rqURI = uri,+ rqMethod = GET,+ rqHeaders = headers,+ rqBody = "" }+ case resp of+ Right res@Response { rspCode = (2,0,0) } -> return (rspBody res)+ Right Response { rspCode = (x,y,z) } ->+ debugFail $ "HTTP " ++ show x ++ show y ++ show z ++ " error getting " ++ show uri+ Left err -> debugFail $ show err++postUrl url body mime = case parseURI url of+ Nothing -> fail $ "Invalid URI: " ++ url+ Just uri -> do debugMessage $ "Posting to HTTP: "++url+ proxy <- getProxy+ when (not $ null proxy) $+ debugFail "No proxy support for HTTP package yet (try libcurl)!"+ resp <- simpleHTTP $ Request { rqURI = uri,+ rqMethod = POST,+ rqHeaders = headers ++ [Header HdrContentType mime,+ Header HdrAccept "text/plain",+ Header HdrContentLength+ (show $ length body) ],+ rqBody = body }+ case resp of+ Right res@Response { rspCode = (2,y,z) } -> do+ putStrLn $ "Success 2" ++ show y ++ show z+ putStrLn (rspBody res)+ return ()+ Right res@Response { rspCode = (x,y,z) } -> do+ putStrLn $ rspBody res+ debugFail $ "HTTP " ++ show x ++ show y ++ show z ++ " error posting to " ++ show uri+ Left err -> debugFail $ show err++requestedUrl :: IORef (String, FilePath)+requestedUrl = unsafePerformIO $ newIORef ("", "")++request_url u f _ = do+ (u', _) <- readIORef requestedUrl+ if null u'+ then do writeIORef requestedUrl (u, f)+ return ""+ else return "URL already requested"++wait_next_url = do+ (u, f) <- readIORef requestedUrl+ if null u+ then return ("", "No URL requested")+ else do writeIORef requestedUrl ("", "")+ e <- (fetchUrl u >>= \s -> B.writeFile f (BC.pack s) >> return "") `catch` h+ return (u, e)+ where h = return . ioeGetErrorString++getProxy :: IO String+getProxy =+ getEnv "http_proxy"+ `catch` \_ -> getEnv "HTTP_PROXY"+ `catch` \_ -> return ""+#else++fetchUrl _ = debugFail "Network.HTTP does not exist"+postUrl _ _ _ = debugFail "Cannot use http POST because darcs was not compiled with Network.HTTP."++request_url _ _ _ = debugFail "Network.HTTP does not exist"+wait_next_url = debugFail "Network.HTTP does not exist"++#endif
@@ -0,0 +1,844 @@+-- Copyright (C) 2003 Peter Simons+-- Copyright (C) 2003 David Roundy+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.+++module IsoDate ( getIsoDateTime, readLocalDate, readUTCDate,+ parseDate, getLocalTz,+ englishDateTime, englishInterval, englishLast,+ iso8601_interval, iso8601_duration,+ cleanLocalDate, resetCalendar,+ MCalendarTime(..), subtractFromMCal, addToMCal,+ toMCalendarTime, unsafeToCalendarTime,+ unsetTime, TimeInterval+ ) where++import Text.ParserCombinators.Parsec+import System.Time+import System.IO.Unsafe ( unsafePerformIO )+import Data.Char ( toUpper, isDigit )+import Data.Maybe ( fromMaybe )+import Control.Monad ( liftM, liftM2 )+import qualified Data.ByteString.Char8 as B++type TimeInterval = (Maybe CalendarTime, Maybe CalendarTime)++-- | Read/interpret a date string, assuming UTC if timezone+-- is not specified in the string (see 'readDate')+-- Warning! This errors out if we fail to interpret the+-- date+readUTCDate :: String -> CalendarTime+readUTCDate = readDate 0++-- | Convert a date string into ISO 8601 format (yyyymmdd variant)+-- assuming local timezone if not specified in the string+-- Warning! This errors out if we fail to interpret the date+cleanLocalDate :: String -> String+cleanLocalDate = showIsoDateTime . resetCalendar+ . readDate (unsafePerformIO getLocalTz)++-- | Read/interpret a date string, assuming local timezone if not+-- specified in the string+readLocalDate :: String -> CalendarTime+readLocalDate = readDate (unsafePerformIO getLocalTz)++-- | Return the local timezone offset from UTC in seconds+getLocalTz :: IO Int+getLocalTz = ctTZ `liftM` (getClockTime >>= toCalendarTime)++-- | Parse a date string with 'parseDate'+-- Warning! This errors out if we fail to interpret the date+-- Uses its first argument as the default time zone.+readDate :: Int -> String -> CalendarTime+readDate tz d =+ case parseDate tz d of+ Left e -> error $ "bad date: "++d++" - "++show e+ Right ct -> resetCalendar $ unsafeToCalendarTime ct++-- | Parse a date string, assuming a default timezone if+-- the date string does not specify one. The date formats+-- understood are those of 'showIsoDateTime' and 'date_time'+parseDate :: Int -> String -> Either ParseError MCalendarTime+parseDate tz d =+ if length d >= 14 && B.all isDigit bd+ then Right $ toMCalendarTime $+ CalendarTime (readI $ B.take 4 bd)+ (toEnum $ (+ (-1)) $ readI $ B.take 2 $ B.drop 4 bd)+ (readI $ B.take 2 $ B.drop 6 bd) -- Day+ (readI $ B.take 2 $ B.drop 8 bd) -- Hour+ (readI $ B.take 2 $ B.drop 10 bd) -- Minute+ (readI $ B.take 2 $ B.drop 12 bd) -- Second+ 0 Sunday 0 -- Picosecond, weekday and day of year unknown+ "GMT" 0 False+ else let dt = do { x <- date_time tz; eof; return x }+ in parse dt "" d+ where bd = B.pack (take 14 d)+ readI s = fst $ fromMaybe (error "parseDate: invalid date") (B.readInt s)++-- | Display a 'CalendarTime' in the ISO 8601 format without any+-- separators, e.g. 20080825142503+showIsoDateTime :: CalendarTime -> String+showIsoDateTime ct = concat [ show $ ctYear ct+ , twoDigit . show . (+1) . fromEnum $ ctMonth ct+ , twoDigit . show $ ctDay ct+ , twoDigit . show $ ctHour ct+ , twoDigit . show $ ctMin ct+ , twoDigit . show $ ctSec ct+ ]+ where twoDigit [] = undefined+ twoDigit x@(_:[]) = '0' : x+ twoDigit x@(_:_:[]) = x+ twoDigit _ = undefined++-- | The current time in the format returned by 'showIsoDateTime'+getIsoDateTime :: IO String+getIsoDateTime = (showIsoDateTime . toUTCTime) `liftM` getClockTime++----- Parser Combinators ---------------------------------------------++-- | Case-insensitive variant of Parsec's 'char' function.+caseChar :: Char -> GenParser Char a Char+caseChar c = satisfy (\x -> toUpper x == toUpper c)++-- | Case-insensitive variant of Parsec's 'string' function.+caseString :: String -> GenParser Char a ()+caseString cs = mapM_ caseChar cs <?> cs++-- | Match a parser at least @n@ times.+manyN :: Int -> GenParser a b c -> GenParser a b [c]+manyN n p+ | n <= 0 = return []+ | otherwise = liftM2 (++) (count n p) (many p)++-- | Match a parser at least @n@ times, but no more than @m@ times.+manyNtoM :: Int -> Int -> GenParser a b c -> GenParser a b [c]+manyNtoM n m p+ | n < 0 = return []+ | n > m = return []+ | n == m = count n p+ | n == 0 = foldr (<|>) (return []) (map (\x -> try $ count x p) (reverse [1..m]))+ | otherwise = liftM2 (++) (count n p) (manyNtoM 0 (m-n) p)+++----- Date/Time Parser -----------------------------------------------++-- | Try each of these date parsers in the following order+--+-- (1) 'cvs_date_time'+--+-- (2) 'iso8601_date_time'+--+-- (3) 'old_date_time+date_time :: Int -> CharParser a MCalendarTime+date_time tz =+ choice [try $ toMCalendarTime `fmap` cvs_date_time tz,+ try $ iso8601_date_time tz,+ toMCalendarTime `fmap` old_date_time]++-- | CVS-style date/times, e.g.+-- 2007/08/25 14:25:39 GMT+-- Note that time-zones are optional here.+cvs_date_time :: Int -> CharParser a CalendarTime+cvs_date_time tz =+ do y <- year+ char '/'+ mon <- month_num + char '/'+ d <- day+ my_spaces+ h <- hour+ char ':'+ m <- minute+ char ':'+ s <- second+ z <- option tz $ my_spaces >> zone+ return (CalendarTime y mon d h m s 0 Monday 0 "" z False)++-- | \"Old\"-style dates, e.g.+-- Tue Jan 3 14:08:07 EST 1999+-- darcs-doc: Question (what does the "old" stand for really?)+old_date_time :: CharParser a CalendarTime+old_date_time = do wd <- day_name+ my_spaces+ mon <- month_name+ my_spaces+ d <- day+ my_spaces+ h <- hour+ char ':'+ m <- minute+ char ':'+ s <- second+ my_spaces+ z <- zone+ my_spaces+ y <- year+ return (CalendarTime y mon d h m s 0 wd 0 "" z False)++-- | ISO 8601 dates and times. Please note the following flaws:+--+-- I am reluctant to implement:+--+-- * years > 9999+--+-- * truncated representations with implied century (89 for 1989) +--+-- I have not implemented:+--+-- * repeated durations (not relevant)+--+-- * lowest order component fractions in intervals+--+-- * negative dates (BC)+--+-- I have not verified or have left too relaxed:+--+-- * the difference between 24h and 0h+--+-- * allows stuff like 2005-1212; either you use the hyphen all the way +-- (2005-12-12) or you don't use it at all (20051212), but you don't use+-- it halfway, likewise with time+--+-- * No bounds checking whatsoever on intervals!+-- (next action: read iso doc to see if bounds-checking required?) -}+iso8601_date_time :: Int -> CharParser a MCalendarTime+iso8601_date_time localTz = try $ + do d <- iso8601_date+ t <- option id $ try $ do optional $ oneOf " T" + iso8601_time + return $ t $ d { mctTZ = Just localTz }++-- | Three types of ISO 8601 date:+--+-- * calendar date, e.g., 1997-07-17, 1997-07, 199707, 1997+--+-- * week+day in year, e.g., 1997-W32-4+--+-- * day in year, e.g, 1997-273+iso8601_date :: CharParser a MCalendarTime+iso8601_date = + do d <- calendar_date <|> week_date <|> ordinal_date+ return $ foldr ($) nullMCalendar d+ where + calendar_date = -- yyyy-mm-dd+ try $ do d <- optchain year_ [ (dash, month_), (dash, day_) ]+ -- allow other variants to be parsed correctly + notFollowedBy (digit <|> char 'W')+ return d+ week_date = --yyyy-Www-d+ try $ do yfn <- year_+ optional dash+ char 'W'+ -- offset human 'week 1' -> computer 'week 0'+ w' <- (\x -> x-1) `liftM` two_digits+ mwd <- option Nothing $ do { optional dash; Just `fmap` n_digits 1 }+ let y = resetCalendar . unsafeToCalendarTime . yfn $ nullMCalendar { mctDay = Just 1 }+ firstDay = ctWDay y+ -- things that make this complicated+ -- 1. iso8601 weeks start from Monday; Haskell weeks start from Sunday+ -- 2. the first week is the one that contains at least Thursday+ -- if the year starts after Thursday, then some days of the year+ -- will have already passed before the first week+ let afterThursday = firstDay == Sunday || firstDay > Thursday+ w = if afterThursday then w'+1 else w'+ yday = (7 * w) + fromMaybe 1 mwd+ diff c = c { mctWeek = True+ , mctWDay = toEnum `fmap` mwd+ , mctDay = Just yday }+ return [(diff.yfn)]+ ordinal_date = -- yyyy-ddd+ try $ optchain year_ [ (dash, yearDay_) ]+ --+ year_ = try $ do y <- four_digits <?> "year (0000-9999)"+ return $ \c -> c { mctYear = Just y }+ month_ = try $ do m <- two_digits <?> "month (1 to 12)"+ return $ \c -> c { mctMonth = Just $ intToMonth m }+ day_ = try $ do d <- two_digits <?> "day in month (1 to 31)"+ return $ \c -> c { mctDay = Just d }+ yearDay_ = try $ do d <- n_digits 3 <?> "day in year (001 to 366)"+ return $ \c -> c { mctDay = Just d+ , mctYDay = Just (d - 1) }+ dash = char '-'++-- | Note that this returns a function which sets the time on+-- another calendar (see 'iso8601_date_time' for a list of+-- flaws+iso8601_time :: CharParser a (MCalendarTime -> MCalendarTime)+iso8601_time = try $+ do ts <- optchain hour_ [ (colon , min_)+ , (colon , sec_)+ , (oneOf ",.", pico_) ] + z <- option id $ choice [ zulu , offset ]+ return $ foldr (.) id (z:ts)+ where + hour_ = do h <- two_digits+ return $ \c -> c { mctHour = Just h }+ min_ = do m <- two_digits+ return $ \c -> c { mctMin = Just m }+ sec_ = do s <- two_digits+ return $ \c -> c { mctSec = Just s }+ pico_ = do digs <- many digit+ let picoExp = 12+ digsExp = length digs+ let frac | null digs = 0+ | digsExp > picoExp = read $ take picoExp digs+ | otherwise = 10 ^ (picoExp - digsExp) * (read digs)+ return $ \c -> c { mctPicosec = Just $ frac }+ zulu = do { char 'Z'; return (\c -> c { mctTZ = Just 0 }) }+ offset = do sign <- choice [ do { char '+' >> return 1 }+ , do { char '-' >> return (-1) } ]+ h <- two_digits+ m <- option 0 $ do { optional colon; two_digits }+ return $ \c -> c { mctTZ = Just $ sign * 60 * ((h*60)+m) }+ colon = char ':'++-- | Intervals in ISO 8601, e.g.,+--+-- * 2008-09/2012-08-17T16:30+--+-- * 2008-09/P2Y11MT16H30M+--+-- * P2Y11MT16H30M/2012-08-17T16:30+--+-- See 'iso8601_duration'+iso8601_interval :: Int -> CharParser a (Either TimeDiff (MCalendarTime, MCalendarTime))+iso8601_interval localTz = leftDur <|> rightDur where+ leftDur = + do dur <- iso8601_duration + end <- option Nothing $ do { char '/'; Just `liftM` isoDt }+ return $ case end of + Nothing -> Left dur+ Just e -> Right (dur `subtractFromMCal` e, e)+ rightDur =+ do start <- isoDt+ char '/'+ durOrEnd <- Left `liftM` iso8601_duration <|> Right `liftM` isoDt+ return $ case durOrEnd of+ Left dur -> Right (start, dur `addToMCal` start)+ Right end -> Right (start, end)+ isoDt = iso8601_date_time localTz+ +-- | Durations in ISO 8601, e.g.,+--+-- * P4Y (four years)+--+-- * P5M (five months)+--+-- * P4Y5M (four years and five months)+--+-- * P4YT3H6S (four years, three hours and six seconds)+iso8601_duration :: CharParser a TimeDiff+iso8601_duration = + do char 'P'+ y <- block 0 'Y'+ mon <- block 0 'M'+ d <- block 0 'D'+ (h,m,s) <- option (0,0,0) $ + do char 'T'+ h' <- block (-1) 'H'+ m' <- block (-1) 'M'+ s' <- block (-1) 'S'+ let unset = (== (-1))+ if all unset [h',m',s'] + then fail "T should be omitted if time is unspecified"+ else let clear x = if (unset x) then 0 else x+ in return (clear h', clear m', clear s')+ --+ return $ TimeDiff y mon d h m s 0+ where block d c = option d $ try $+ do n <- many1 digit+ char c + return $ read n++-- | 'optchain' @p xs@ parses a string with the obligatory+-- parser @p@. If this suceeds, it continues on to the+-- rest of the input using the next parsers down the+-- chain. Each part of the chain consists of a parser+-- for a separator and for the content itself. The+-- separator is optional.+--+-- A good use of this function is to help in parsing ISO+-- ISO 8601 dates and times. For example, the parser+-- @optchain year [(dash, month), (dash, day)]@ accepts+-- dates like 2007 (only the year is used), 2007-07 (only+-- the year and month), 200707 (only the year and month+-- with no separator), 2007-07-19 (year, month and day).+optchain :: CharParser a b -> [(CharParser a c, CharParser a b)] -> CharParser a [b]+optchain p next = try $ + do r1 <- p+ r2 <- case next of + [] -> return []+ ((sep,p2):next2) -> option [] $ do { optional sep; optchain p2 next2 }+ return (r1:r2)++n_digits :: Int -> CharParser a Int +n_digits n = read `liftM` count n digit++two_digits, four_digits :: CharParser a Int+two_digits = n_digits 2+four_digits = n_digits 4++-- | One or more space.+-- WARNING! This only matches on the space character, not on+-- whitespace in general+my_spaces :: CharParser a String+my_spaces = manyN 1 $ char ' '++-- | English three-letter day abbreviations (e.g. Mon, Tue, Wed)+day_name :: CharParser a Day+day_name = choice+ [ caseString "Mon" >> return Monday+ , try (caseString "Tue") >> return Tuesday+ , caseString "Wed" >> return Wednesday+ , caseString "Thu" >> return Thursday+ , caseString "Fri" >> return Friday+ , try (caseString "Sat") >> return Saturday+ , caseString "Sun" >> return Sunday+ ]++-- | Four-digit year+year :: CharParser a Int+year = four_digits++-- | One or two digit month (e.g. 3 for March, 11 for November)+month_num :: CharParser a Month+month_num = do mn <- manyNtoM 1 2 digit + return $ intToMonth $ (read mn :: Int)++-- | January is 1, February is 2, etc+intToMonth :: Int -> Month+intToMonth 1 = January+intToMonth 2 = February+intToMonth 3 = March+intToMonth 4 = April+intToMonth 5 = May+intToMonth 6 = June+intToMonth 7 = July+intToMonth 8 = August+intToMonth 9 = September+intToMonth 10 = October+intToMonth 11 = November+intToMonth 12 = December+intToMonth _ = error "invalid month!"++-- | English three-letter month abbreviations (e.g. Jan, Feb, Mar)+month_name :: CharParser a Month+month_name = choice+ [ try (caseString "Jan") >> return January+ , caseString "Feb" >> return February+ , try (caseString "Mar") >> return March+ , try (caseString "Apr") >> return April+ , caseString "May" >> return May+ , try (caseString "Jun") >> return June+ , caseString "Jul" >> return July+ , caseString "Aug" >> return August+ , caseString "Sep" >> return September+ , caseString "Oct" >> return October+ , caseString "Nov" >> return November+ , caseString "Dec" >> return December+ ]++-- | day in one or two digit notation+day :: CharParser a Int+day = do d <- manyNtoM 1 2 digit+ return (read d :: Int)++-- | hour in two-digit notation+hour :: CharParser a Int+hour = two_digits++-- | minute in two-digit notation+minute :: CharParser a Int+minute = two_digits++-- | second in two-digit notation+second :: CharParser a Int+second = two_digits++-- | limited timezone support+--+-- * +HHMM or -HHMM+--+-- * Universal timezones: UTC, UT+--+-- * Zones from GNU coreutils/lib/getdate.y, less half-hour ones --+-- sorry Newfies.+--+-- * any sequence of alphabetic characters (WARNING! treated as 0!)+zone :: CharParser a Int+zone = choice+ [ do { char '+'; h <- hour; m <- minute; return (((h*60)+m)*60) }+ , do { char '-'; h <- hour; m <- minute; return (-((h*60)+m)*60) }+ , mkZone "UTC" 0+ , mkZone "UT" 0+ , mkZone "GMT" 0+ , mkZone "WET" 0+ , mkZone "WEST" 1+ , mkZone "BST" 1+ , mkZone "ART" (-3)+ , mkZone "BRT" (-3)+ , mkZone "BRST" (-2)+ , mkZone "AST" (-4)+ , mkZone "ADT" (-3)+ , mkZone "CLT" (-4)+ , mkZone "CLST" (-3)+ , mkZone "EST" (-5)+ , mkZone "EDT" (-4)+ , mkZone "CST" (-6)+ , mkZone "CDT" (-5)+ , mkZone "MST" (-7)+ , mkZone "MDT" (-6)+ , mkZone "PST" (-8)+ , mkZone "PDT" (-7)+ , mkZone "AKST" (-9)+ , mkZone "AKDT" (-8)+ , mkZone "HST" (-10)+ , mkZone "HAST" (-10)+ , mkZone "HADT" (-9)+ , mkZone "SST" (-12)+ , mkZone "WAT" 1+ , mkZone "CET" 1+ , mkZone "CEST" 2+ , mkZone "MET" 1+ , mkZone "MEZ" 1+ , mkZone "MEST" 2+ , mkZone "MESZ" 2+ , mkZone "EET" 2+ , mkZone "EEST" 3+ , mkZone "CAT" 2+ , mkZone "SAST" 2+ , mkZone "EAT" 3+ , mkZone "MSK" 3+ , mkZone "MSD" 4+ , mkZone "SGT" 8+ , mkZone "KST" 9+ , mkZone "JST" 9+ , mkZone "GST" 10+ , mkZone "NZST" 12+ , mkZone "NZDT" 13+ -- if we don't understand it, just give a GMT answer...+ , do { manyTill (oneOf $ ['a'..'z']++['A'..'Z']++[' '])+ (lookAhead space_digit);+ return 0 }+ ]+ where mkZone n o = try $ do { caseString n; return (o*60*60) }+ space_digit = try $ do { char ' '; oneOf ['0'..'9'] }++----- English dates and intervals -----------------------------------------------++-- | In English, either a date followed by a time, or vice-versa, e.g,+--+-- * yesterday at noon+--+-- * yesterday tea time+--+-- * 12:00 yesterday+--+-- See 'englishDate' and 'englishTime'+-- Uses its first argument as "now", i.e. the time relative to which+-- "yesterday", "today" etc are to be interpreted+englishDateTime :: CalendarTime -> CharParser a CalendarTime+englishDateTime now = + try $ dateMaybeAtTime <|> timeThenDate + where + -- yesterday (at) noon+ dateMaybeAtTime = try $ + do ed <- englishDate now+ t <- option Nothing $ try $+ do { space; optional $ caseString "at "; Just `liftM` englishTime }+ return $ fromMaybe id t $ ed+ -- tea time 2005-12-04+ timeThenDate = try $+ do t <- englishTime + optional $ char ','+ space+ ed <- englishDate now+ return $ t $ unsetTime $ ed++-- | Specific dates in English as specific points of time, e.g,+--+-- * today+--+-- * yesterday+--+-- * last week (i.e. the beginning of that interval)+--+-- * 4 months ago (via 'englishAgo')+--+-- The first argument is "now".+englishDate :: CalendarTime -> CharParser a CalendarTime+englishDate now = try $ + (caseString "today" >> (return $ resetCalendar now))+ <|> (caseString "yesterday" >> (return $ oneDay `subtractFromCal` now) )+ <|> fst `fmap` englishLast now+ <|> englishAgo now+ where oneDay = TimeDiff 0 0 1 0 0 0 0++-- | English expressions for points in the past, e.g.+--+-- * 4 months ago+--+-- * 1 day ago+--+-- * day before yesterday+--+-- See 'englishDuration'+englishAgo :: CalendarTime -> CharParser a CalendarTime+englishAgo now =+ try $ do p <- englishDuration+ try $ do space+ (m,ref) <- (try $ caseString "ago" >> return ((-1), now))+ <|> do m <- beforeMod <|> afterMod+ space+ d <- englishDate now+ <|> fst `fmap` englishLast now+ <|> unsafeToCalendarTime `fmap` iso8601_date_time (ctTZ now)+ return (m,d)+ return $ multiplyDiff m p `addToCal` ref+ where+ beforeMod = try $ caseString "before" >> return (-1)+ afterMod = try $ caseString "after" >> return 1++-- | English expressions for intervals of time,+--+-- * before tea time (i.e. from the beginning of time)+--+-- * after 14:00 last month (i.e. till now)+--+-- * between last year and last month+--+-- * in the last three months (i.e. from then till now)+--+-- * 4 months ago (i.e. till now; see 'englishAgo')+englishInterval :: CalendarTime -> CharParser a TimeInterval+englishInterval now = twixt <|> before <|> after <|> inTheLast <|> lastetc+ where + englishDT = (unsafeToCalendarTime `fmap` iso8601_date_time (ctTZ now)+ <|> englishDateTime now)+ before = try $+ do caseString "before"+ space+ end <- englishDT + return (Just theBeginning, Just end)+ after = try $+ do caseString "after"+ space+ start <- englishDT+ return (Just start, Nothing)+ twixt = try $ + do caseString "between"+ space+ start <- englishDT+ space+ caseString "and" + space+ end <- englishDT + return (Just start, Just end)+ inTheLast = try $+ do caseString "in the last"+ space+ dur <- englishDuration+ return (Just $ dur `subtractFromCal` now, Just now)+ lastetc =+ do l <- englishAgo now+ return (Just l, Just now)++-- | Durations in English that begin with the word \"last\",+-- E.g. \"last 4 months\" is treated as the duration between+-- 4 months ago and now+englishLast :: CalendarTime -> CharParser a (CalendarTime, CalendarTime)+englishLast now =+ -- last year, last week, last 3 years, etc+ try $ do caseString "last"+ space+ d <- englishDuration+ return (d `subtractFromCal` now, now)++-- | Either an 'iso8601_time' or one of several common+-- English time expressions like 'noon' or 'tea time'+englishTime :: CharParser a (CalendarTime->CalendarTime)+englishTime = try $ + choice [ wrapM `fmap` iso8601_time+ , namedTime "noon" 12 0 + , namedTime "midnight" 0 0+ , namedTime "tea time" 16 30+ , namedTime "bed time" 2 30 + , namedTime "proper bed time" 21 30 ]+ where namedTime name h m = try $ + do caseString name+ return $ \c -> c { ctHour = h, ctMin = m }+ wrapM f = unsafeToCalendarTime . f . toMCalendarTime++-- | Some English durations, e.g.+--+-- * day+--+-- * 4 score+--+-- * 7 years+--+-- * 12 months+--+-- This is not particularly strict about what it accepts.+-- For example, "7 yeares", "4 scores" or "1 days" are+-- just fine.+englishDuration :: CharParser a TimeDiff+englishDuration = try $+ do n <- option 1 $ do { x <- many1 digit; space; (return $ read x) }+ b <- base+ optional (caseString "es" <|> caseString "s")+ let current = multiplyDiff n b+ next <- option noTimeDiff $ try $ do + { optional space; char ',' ; optional space ; englishDuration }+ return $ addDiff current next + where + base = choice + [ try $ caseString "score" >> (return $ TimeDiff 20 0 0 0 0 0 0) -- why not?+ , caseString "year" >> (return $ TimeDiff 1 0 0 0 0 0 0)+ , try $ caseString "month" >> (return $ TimeDiff 0 1 0 0 0 0 0)+ , caseString "fortnight" >> (return $ TimeDiff 0 0 14 0 0 0 0)+ , caseString "week" >> (return $ TimeDiff 0 0 7 0 0 0 0)+ , caseString "day" >> (return $ TimeDiff 0 0 1 0 0 0 0)+ , caseString "hour" >> (return $ TimeDiff 0 0 0 1 0 0 0)+ , caseString "minute" >> (return $ TimeDiff 0 0 0 0 1 0 0) + , caseString "second" >> (return $ TimeDiff 0 0 0 0 0 1 0) ]+ +----- Calendar and TimeDiff manipulation ---------------------------------------------++-- | The very beginning of time, i.e. 1970-01-01+theBeginning :: CalendarTime+theBeginning = unsafePerformIO $ toCalendarTime $ TOD 0 0++-- | An 'MCalenderTime' is an underspecified 'CalendarTime'+-- It is used for parsing dates. For example, if you want to parse+-- the date '4 January', it may be useful to underspecify the year+-- by setting it to 'Nothing'. This uses almost the same fields as+-- 'System.Time.CalendarTime', a notable exception being that we+-- introduce 'mctWeek' to indicate if a weekday was specified or not+data MCalendarTime = MCalendarTime+ { mctYear :: Maybe Int+ , mctMonth :: Maybe Month+ , mctDay :: Maybe Int+ , mctHour :: Maybe Int+ , mctMin :: Maybe Int+ , mctSec :: Maybe Int+ , mctPicosec :: Maybe Integer+ , mctWDay :: Maybe Day+ , mctYDay :: Maybe Int+ , mctTZName :: Maybe String+ , mctTZ :: Maybe Int+ , mctIsDST :: Maybe Bool+ , mctWeek :: Bool -- is set or not+} deriving Show++-- | Trivially convert a 'CalendarTime' to a fully specified+-- 'MCalendarTime' (note that this sets the 'mctWeek' flag to+-- @False@+toMCalendarTime :: CalendarTime -> MCalendarTime+toMCalendarTime (CalendarTime a b c d e f g h i j k l) =+ MCalendarTime (Just a) (Just b) (Just c) (Just d) (Just e) (Just f)+ (Just g) (Just h) (Just i) (Just j) (Just k) (Just l)+ False++-- | Returns the first 'CalendarTime' that falls within a 'MCalendarTime'+-- This is only unsafe in the sense that it plugs in default values+-- for fields that have not been set, e.g. @January@ for the month+-- or @0@ for the seconds field.+-- Maybe we should rename it something happier.+-- See also 'resetCalendar'+unsafeToCalendarTime :: MCalendarTime -> CalendarTime+unsafeToCalendarTime m =+ CalendarTime+ { ctYear = fromMaybe 0 $ mctYear m+ , ctMonth = fromMaybe January $ mctMonth m+ , ctDay = fromMaybe 1 $ mctDay m+ , ctHour = fromMaybe 0 $ mctHour m+ , ctMin = fromMaybe 0 $ mctMin m+ , ctSec = fromMaybe 0 $ mctSec m+ , ctPicosec = fromMaybe 0 $ mctPicosec m+ , ctWDay = fromMaybe Sunday $ mctWDay m+ , ctYDay = fromMaybe 0 $ mctYDay m+ , ctTZName = fromMaybe "" $ mctTZName m+ , ctTZ = fromMaybe 0 $ mctTZ m+ , ctIsDST = fromMaybe False $ mctIsDST m+ }++addToCal :: TimeDiff -> CalendarTime -> CalendarTime+addToCal td = toUTCTime . addToClockTime td . toClockTime++subtractFromCal :: TimeDiff -> CalendarTime -> CalendarTime+subtractFromCal = addToCal . multiplyDiff (-1)++addToMCal :: TimeDiff -> MCalendarTime -> MCalendarTime+addToMCal td mc =+ copyCalendar (addToCal td $ unsafeToCalendarTime mc) mc++subtractFromMCal :: TimeDiff -> MCalendarTime -> MCalendarTime+subtractFromMCal = addToMCal . multiplyDiff (-1)++-- surely there is a more concise way to express these+addDiff :: TimeDiff -> TimeDiff -> TimeDiff+addDiff (TimeDiff a1 a2 a3 a4 a5 a6 a7) (TimeDiff b1 b2 b3 b4 b5 b6 b7) =+ TimeDiff (a1+b1) (a2+b2) (a3+b3) (a4+b4) (a5+b5) (a6+b6) (a7 + b7)++-- | 'multiplyDiff' @i d@ multiplies every field in @d@ with @i@+--+-- FIXME; this seems like a terrible idea! it seems like+-- we should get rid of it if at all possible, maybe adding an+-- invertDiff function+multiplyDiff :: Int -> TimeDiff -> TimeDiff+multiplyDiff m (TimeDiff a1 a2 a3 a4 a5 a6 a7) =+ TimeDiff (a1*m) (a2*m) (a3*m) (a4*m) (a5*m) (a6*m) (a7 * (toInteger m))++nullMCalendar :: MCalendarTime+nullMCalendar = MCalendarTime Nothing Nothing Nothing Nothing Nothing Nothing+ Nothing Nothing Nothing Nothing Nothing Nothing+ False++-- | Set a calendar to UTC time any eliminate any inconsistencies within+-- (for example, where the weekday is given as @Thursday@, but this does not+-- match what the numerical date would lead one to expect)+resetCalendar :: CalendarTime -> CalendarTime+resetCalendar = toUTCTime . toClockTime++-- | 'copyCalendar' @c mc@ replaces any field which is+-- specified in @mc@ with the equivalent field in @c@+-- @copyCalendar c nullMCalendar == nullMCalendar@+copyCalendar :: CalendarTime -> MCalendarTime -> MCalendarTime+copyCalendar c mc = mc+ { mctYear = mctYear mc >> Just (ctYear c)+ , mctMonth = mctMonth mc >> Just (ctMonth c)+ , mctDay = mctDay mc >> Just (ctDay c)+ , mctHour = mctHour mc >> Just (ctHour c)+ , mctMin = mctMin mc >> Just (ctMin c)+ , mctSec = mctSec mc >> Just (ctSec c)+ , mctPicosec = mctPicosec mc >> Just (ctPicosec c)+ , mctWDay = mctWDay mc >> Just (ctWDay c)+ , mctYDay = mctYDay mc >> Just (ctYDay c)+ , mctTZName = mctTZName mc >> Just (ctTZName c)+ , mctTZ = mctTZ mc >> Just (ctTZ c)+ , mctIsDST = mctIsDST mc >> Just (ctIsDST c)+ }++-- | Zero the time fields of a 'CalendarTime'+unsetTime :: CalendarTime -> CalendarTime+unsetTime mc = mc+ { ctHour = 0+ , ctMin = 0+ , ctSec = 0+ , ctPicosec = 0+ }
@@ -0,0 +1,478 @@+-- Copyright (C) 2002 David Roundy+-- Copyright (C) 2005 Benedikt Schmidt+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.++{-# OPTIONS_GHC -cpp #-}+{-# LANGUAGE CPP #-}++-- | LCS stands for Longest Common Subsequence, and it is a relatively+-- challenging problem to find an LCS efficiently. This module implements+-- the algorithm described in:+--+-- "An O(ND) Difference Algorithm and its Variations", Eugene Myers,+-- Algorithmica Vol. 1 No. 2, 1986, pp. 251-266;+-- especially the variation described in section 4.2 and most refinements+-- implemented in GNU diff (D is the edit-distance).+--+-- There is currently no heuristic to reduce the running time and produce+-- suboptimal output for large inputs with many differences. It behaves like+-- GNU diff with the -d option in this regard.+--+-- In the first step, a hash value for every line is calculated and collisions+-- are marked with a special value. This reduces a string comparison to an+-- int comparison for line tuples where at least one of the hash values is+-- not equal to the special value. After that, lines which only exists in one+-- of the files are removed and marked as changed which reduces the running+-- time of the following difference algorithm. GNU diff additionally removes+-- lines that appear very often in the other file in some cases.+-- The last step tries to create longer changed regions and line up deletions+-- in the first file to insertions in the second by shifting changed lines+-- forward and backward.+module Lcs ( getChanges, aLen,+ BArray, PArray, BSTArray,+ shiftBoundaries ) where++import Control.Monad+import Data.Int+import Control.Monad.ST+import Data.Maybe+import ByteStringUtils (hashPS)+import qualified Data.ByteString as B (empty, ByteString)+import Data.Array.Base+import Data.Array.Unboxed+import qualified Data.Map as Map ( lookup, empty, insertWith )+#include "impossible.h"++-- | create a list of changes between a and b, each change has the form+-- (starta, lima, startb, limb) which means that a[starta, lima)+-- has to be replaced by b[startb, limb)+getChanges :: [B.ByteString] -> [B.ByteString]+ -> [(Int,[B.ByteString],[B.ByteString])]+getChanges a b = dropStart (initP a) (initP b) 1++dropStart :: PArray -> PArray -> Int+ -> [(Int,[B.ByteString],[B.ByteString])]+dropStart a b off+ | off > (aLen a) = [(off - 1, [], getSlice b off (aLen b))]+ | off > (aLen b) = [(off - 1, getSlice a off (aLen a), [])]+ | a!off == b!off = dropStart a b (off + 1)+ | otherwise = dropEnd a b off 0++dropEnd :: PArray -> PArray -> Int -> Int+ -> [(Int,[B.ByteString],[B.ByteString])]+dropEnd a b off end+ | off > alast = [(off - 1, [], getSlice b off blast)]+ | off > blast = [(off - 1, getSlice a off alast, [])]+ | a!alast == b!blast = dropEnd a b off (end + 1)+ | otherwise = getChanges' (a, (off, alast)) (b, (off, blast))+ where alast = aLen a - end+ blast = aLen b - end++getSlice :: PArray -> Int -> Int -> [B.ByteString]+getSlice a from to+ | from > to = []+ | otherwise = (a!(from)):(getSlice a (from + 1) to)++getChanges' :: (PArray, (Int, Int)) -> (PArray, (Int, Int))+ -> [(Int,[B.ByteString],[B.ByteString])]+getChanges' (a, abounds) (b, bbounds) =+ map (convertPatch 0 a b) $ createPatch c_a c_b+ where+ -- If the last few characters of two lines are the same, the lines are+ -- probably the same. The choice of 20 is plucked out of the air.+ toHash x bnds = listArray bnds [ hashPS $ x!i | i <- range bnds]+ ah = toHash a abounds :: HArray+ mkAMap m (i:is) =+ let ins (_,_,_,new) (collision,_,_,old) =+ (collision || not (new == old), True, False, old)+ m' = Map.insertWith ins (ah!i) (False, True, False, a!i) m+ in mkAMap m' is+ mkAMap m _ = m+ hm_a = mkAMap Map.empty (range abounds)+ --+ bh = toHash b bbounds :: HArray+ mkBMap m (i:is) =+ let ins (_,_,_,new) (collision,in_a,_,old) =+ (collision || not (new == old), in_a, True, old)+ m' = Map.insertWith ins (bh!i) (False, False, True, b!i) m+ in mkBMap m' is+ mkBMap m _ = m+ hm = mkBMap hm_a (range bbounds)+ -- take care of collisions, if there are different lines with the+ -- same hash in both files, then set the hash to markColl,+ -- PackedStrings are compared for two lines with the hash markColl+ get (i, h) = case Map.lookup h hm of+ Just (_,False,_,_) -> Nothing+ Just (_,_,False,_) -> Nothing+ Just (False,True,True,_) -> Just (i, h)+ Just (True,True,True,_) -> Just (i, markColl)+ Nothing -> impossible++ a' = catMaybes $ map get [(i, ah!i) | i <- range (bounds ah)]+ b' = catMaybes $ map get [(i, bh!i) | i <- range (bounds bh)]++ (c_a, c_b) = diffArr a' b' (a, abounds) (b, bbounds)++-- | mark hash value where collision occured+markColl :: Int32+markColl = 2345677++-- | return arrays with changes in a and b (1 indexed), offsets start with 0+diffArr :: [(Int,Int32)] -> [(Int,Int32)]+ -> (PArray, (Int, Int)) -> (PArray, (Int, Int))+ -> (BArray, BArray)+diffArr a b (p_a, (off_a, l_a)) (p_b, (off_b, l_b)) = runST (+ do let h_a = initH (map snd a)+ h_b = initH (map snd b)+ m_a = initM (map fst a)+ m_b = initM (map fst b)+ end_a = (aLen p_a)+ end_b = (aLen p_b)+ c_a <- initVChanged end_a+ c_b <- initVChanged end_b+ mapM_ (\ (l,_) -> writeArray c_a l False) $ a+ mapM_ (\ (l,_) -> writeArray c_b l False) $ b+ _ <- cmpseq h_a h_b p_a p_b m_a m_b c_a c_b 0 0 (aLen h_a) (aLen h_b)+ let unchanged ar = do {xs <- getElems ar; return $ (length $ filter not $ xs) -1}+ err <- liftM2 (/=) (unchanged c_a) (unchanged c_b)+ when (err) impossible+ -- Mark common lines at beginning and end+ mapM_ (\ i -> writeArray c_a i False ) $ [1..(off_a - 1)]+ mapM_ (\ i -> writeArray c_b i False ) $ [1..(off_b - 1)]+ mapM_ (\ i -> writeArray c_a i False ) $ [(l_a + 1) .. (end_a)]+ mapM_ (\ i -> writeArray c_b i False ) $ [(l_b + 1) .. (end_b)]+ shiftBoundaries c_a c_b p_a 1 1+ shiftBoundaries c_b c_a p_b 1 1+ err1 <- liftM2 (/=) (unchanged c_a) (unchanged c_b)+ when (err1) impossible+ c_a' <- unsafeFreeze c_a+ c_b' <- unsafeFreeze c_b+ return (c_a', c_b'))++-- | set changes array for a and b and return number of changed lines+cmpseq :: HArray -> HArray -> PArray -> PArray -> MapArray -> MapArray+ -> BSTArray s -> BSTArray s -> Int -> Int -> Int -> Int -> ST s Int+cmpseq _ _ _ _ _ _ _ _ _ _ 0 0 = do return 0+cmpseq h_a h_b p_a p_b m_a m_b c_a c_b off_a off_b l_a l_b = do+ let lim_a = off_a+l_a+ lim_b = off_b+l_b+ off_a' = findSnake h_a h_b p_a p_b m_a m_b off_a off_b l_a l_b off_a off_b+ off_b' = off_b+off_a'-off_a+ lim_a' = findSnakeRev h_a h_b p_a p_b m_a m_b lim_a lim_b off_a' off_b'+ lim_b' = lim_b+lim_a'-lim_a+ l_a' = lim_a'-off_a'+ l_b' = lim_b'-off_b'+ if l_a' == 0 || l_b' == 0+ then if l_a' == 0+ then do when (l_b' > 0) $+ mapM_ (\i -> writeArray c_b (m_b!i) True)+ [(off_b' + 1) .. lim_b']+ return l_b'+ else do when (l_a' > 0) $+ mapM_ (\i -> writeArray c_a (m_a!i) True)+ [(off_a' + 1) .. lim_a']+ return l_a'+ else do let m = l_a' + l_b'+ del = l_a' - l_b'+ dodd = odd $ del+ v <- initV m+ vrev <- initVRev m l_a'+ writeArray vrev 0 l_a'+ writeArray v 0 0+ (xmid, ymid, _) <- findDiag 1 h_a h_b p_a p_b m_a m_b v vrev+ off_a' off_b' l_a' l_b' del dodd+ when ((xmid == 0 && ymid == 0) || (xmid == l_a' && ymid == l_b')+ || (xmid < 0 || ymid < 0 || xmid > l_a' || ymid > l_b')) + impossible+ c1 <- cmpseq h_a h_b p_a p_b m_a m_b c_a c_b+ off_a' off_b' xmid ymid+ c2 <- cmpseq h_a h_b p_a p_b m_a m_b c_a c_b+ (off_a' + xmid) (off_b' + ymid)+ (l_a' - xmid) (l_b' - ymid)+ return $ c1 + c2++-- | return (xmid, ymid, cost) for the two substrings+-- a[off_a+1..off_a+1+l_a] and b+findDiag :: Int -> HArray -> HArray -> PArray -> PArray -> MapArray -> MapArray+ -> VSTArray s -> VSTArray s -> Int -> Int -> Int -> Int -> Int -> Bool+ -> ST s (Int, Int, Int)+findDiag c h_a h_b p_a p_b m_a m_b v vrev off_a off_b l_a l_b del dodd = do+ if c > l_a + l_b then error "findDiag failed" else return ()+ r <- findF+ case r of+ Just (xmid, ymid) -> return (xmid, ymid, (c*2 - 1))+ Nothing ->+ do r' <- findR+ case r' of+ Just (xmid, ymid) -> return (xmid, ymid, c*2)+ Nothing -> findDiag (c + 1) h_a h_b p_a p_b m_a m_b v vrev+ off_a off_b l_a l_b del dodd+ where fdmax = if c <= l_a then c else l_a - ((l_a + c) `mod` 2)+ rdmax = if c <= l_b then c else l_b - ((l_b + c) `mod` 2)+ lastrdmax = if (c-1) <= l_b then c-1 else l_b-((l_b + (c-1) `mod` 2))+ lastrdmin = -(if (c-1) <= l_a then c-1 else l_a-((l_a + (c-1)) `mod` 2))+ fdmin = -rdmax+ rdmin = -fdmax+ findF = findF' fdmax+ findR = findR' rdmax+ findF' d = do x <- findOne h_a h_b p_a p_b m_a m_b v d off_a off_b l_a l_b+ if dodd && d - del >= lastrdmin && d - del <= lastrdmax+ then do xr <- readArray vrev (d - del)+ if xr <= x then return $ Just (x, x - d)+ else if d <= fdmin then return Nothing+ else findF' (d-2)+ else if d <= fdmin then return Nothing else findF' (d-2)+ findR' d = do x <- findOneRev h_a h_b p_a p_b m_a m_b vrev d del off_a off_b+ if not dodd && (d + del >= fdmin) && (d + del <= fdmax)+ then do xf <- readArray v (d + del)+ if x <= xf then return $ Just (x,x-del-d)+ else if d <= rdmin then return Nothing+ else findR' (d-2)+ else if d <= rdmin then return Nothing else findR' (d-2)++-- | find position on diag d with one more insert/delete going forward+findOne :: HArray -> HArray -> PArray -> PArray -> MapArray -> MapArray+ -> VSTArray s -> Int -> Int -> Int -> Int -> Int -> ST s Int +findOne h_a h_b p_a p_b m_a m_b v d off_a off_b l_a l_b = do+ x0 <- do xbelow <- readArray v (d - 1)+ xover <- readArray v (d + 1)+ return $ if xover > xbelow then xover else xbelow + 1+ let y0 = x0 - d+ x = findSnake h_a h_b p_a p_b m_a m_b (x0+off_a) (y0+off_b)+ l_a l_b off_a off_b+ writeArray v d (x - off_a)+ return (x-off_a)++-- | follow snake from northwest to southeast, x and y are absolute positions+findSnake :: HArray -> HArray -> PArray -> PArray -> MapArray -> MapArray+ -> Int -> Int -> Int -> Int -> Int -> Int -> Int+findSnake h_a h_b p_a p_b m_a m_b x y l_a l_b off_a off_b =+ if x < l_a + off_a && y < l_b + off_b && h_a!(x+1) == h_b!(y+1)+ && (h_a!(x+1) /= markColl || p_a!(m_a!(x+1)) == p_b!(m_b!(y+1)))+ then findSnake h_a h_b p_a p_b m_a m_b (x + 1) (y + 1) l_a l_b off_a off_b+ else x++-- | find position on diag d with one more insert/delete going backward+findOneRev :: HArray -> HArray -> PArray -> PArray -> MapArray -> MapArray+ -> VSTArray s -> Int -> Int -> Int -> Int -> ST s Int +findOneRev h_a h_b p_a p_b m_a m_b v d del off_a off_b = do+ x0 <- do xbelow <- readArray v (d - 1)+ xover <- readArray v (d + 1)+ return $ if xbelow < xover then xbelow else xover-1+ let y0 = x0 - del - d+ x = findSnakeRev h_a h_b p_a p_b m_a m_b (x0+off_a) (y0+off_b)+ off_a off_b+ writeArray v d (x-off_a)+ return (x-off_a)++-- | follow snake from southeast to northwest, x and y are absolute positions+findSnakeRev :: HArray -> HArray -> PArray -> PArray -> MapArray -> MapArray+ -> Int -> Int -> Int -> Int -> Int+findSnakeRev h_a h_b p_a p_b m_a m_b x y off_a off_b =+ if x > off_a && y > off_b && h_a!x == h_b!y+ && (h_a!x /= markColl || p_a!(m_a!x) == p_b!(m_b!y))+ then findSnakeRev h_a h_b p_a p_b m_a m_b (x - 1) (y - 1) off_a off_b+ else x++-- | try to create nicer diffs by shifting around regions of changed lines+shiftBoundaries :: BSTArray s -> BSTArray s -> PArray -> Int -> Int -> ST s ()+shiftBoundaries c_a c_b p_a i_ j_ =+ do x <- nextChanged c_a i_+ case x of+ Just start ->+ do let skipped = start - i_+ j1 <- nextUnchangedN c_b skipped j_+ end <- nextUnchanged c_a start+ j2 <- nextUnchanged c_b j1+ (i3,j3) <- expand start end j2+ shiftBoundaries c_a c_b p_a i3 j3+ Nothing -> return () -- no change up to end of file+ where noline = (aLen p_a) + 1+ expand start i j =+ do let len = i - start+ (start0,i0,j0) <- shiftBackward start i j+ b <- if j0 > 1 then readArray c_b (j0-1) else return False+ let corr = if b then i0 else noline+ let blank = if p_a!(i0-1) == B.empty then i0+ else noline+ (start1,i1,j1,corr1,blank1) <- shiftForward start0 i0 j0 corr blank+ -- prefer corresponding to ending with blank line+ let newi = if corr1 == noline then blank1+ else corr1+ (start2,i2,j2) <- moveCorr start1 i1 j1 newi+ if len /= i2 - start2+ then expand start2 i2 j2+ else return (i2, j2)+ shiftBackward start i j =+ if start > 1 && p_a!(i-1) == p_a!(start-1)+ then do when (i == start) impossible+ b1 <- readArray c_a (i-1)+ b2 <- readArray c_a (start-1)+ when ((not b1) || b2) impossible+ writeArray c_a (i-1) False+ writeArray c_a (start-1) True+ b <- if start > 2 then readArray c_a (start-2)+ else return False+ start' <- if b then liftM (1+) (prevUnchanged c_a (start-2))+ else return (start-1)+ j' <- prevUnchanged c_b (j-1)+ shiftBackward start' (i-1) j'+ else do return (start,i,j)+ shiftForward start i j corr blank =+ if i <= aLen p_a && p_a!i == p_a!start &&+ -- B.empty at the end of file marks empty line after final newline+ not ((i == aLen p_a) && (p_a!i == B.empty))+ then do when (i == start) impossible+ b1 <- readArray c_a i+ b2 <- readArray c_a start+ when ((not b2) || b1) impossible+ writeArray c_a i True+ writeArray c_a start False+ i0 <- nextUnchanged c_a (i+1)+ j0 <- nextUnchanged c_b (j+1)+ let corr0 = if i0 > (i+1) then noline+ else if j0-j > 2 then i0 else corr+ let blank0 = if i0 > i+1 then noline+ else if p_a!(i0-1) == B.empty then i0+ else blank+ shiftForward (start+1) i0 j0 corr0 blank0+ else do return (start,i,j,corr,blank)+ moveCorr start i j corr =+ if corr >= i+ then return (start,i,j)+ else do b1 <- readArray c_a (i-1)+ b2 <- readArray c_a (start-1)+ when ((not b1) || b2) impossible+ when (p_a!(i-1) /= p_a!(start-1)) impossible+ writeArray c_a (i-1) False+ writeArray c_a (start-1) True + j' <- prevUnchanged c_b (j-1)+ moveCorr (start-1) (i-1) j' corr++-- | goto next unchanged line, return the given line if unchanged+nextUnchanged :: BSTArray s -> Int -> ST s Int+nextUnchanged c i = do+ len <- aLenM c+ if i == len + 1 then return i+ else do b <- readArray c i+ if b then nextUnchanged c (i+1)+ else return i++-- | skip at least one unchanged line, if there is none advance+-- behind the last line+skipOneUnChanged :: BSTArray s -> Int -> ST s Int+skipOneUnChanged c i = do+ len <- aLenM c+ if i == len + 1+ then return i+ else do b <- readArray c i+ if not b then return (i+1)+ else skipOneUnChanged c (i+1)++-- | goto n-th next unchanged line+nextUnchangedN :: BSTArray s -> Int -> Int -> ST s Int+nextUnchangedN c n i = do+ if n == 0 then return i+ else do i' <- skipOneUnChanged c i+ nextUnchangedN c (n-1) i'++-- | goto next changed line, return the given line if changed+nextChanged :: BSTArray s -> Int -> ST s (Maybe Int)+nextChanged c i = do+ len <- aLenM c+ if i <= len+ then do b <- readArray c i+ if not b then nextChanged c (i+1)+ else return $ Just i+ else return Nothing++-- | goto previous unchanged line, return the given line if unchanged+prevUnchanged :: BSTArray s -> Int -> ST s Int+prevUnchanged c i = do+ b <- readArray c i+ if b then prevUnchanged c (i-1)+ else return i++type HArray = UArray Int Int32+type BArray = UArray Int Bool+type PArray = Array Int B.ByteString+type MapArray = UArray Int Int+type VSTArray s = STUArray s Int Int+type BSTArray s = STUArray s Int Bool++initV :: Int -> ST s (VSTArray s)+initV dmax = do+ newArray (-(dmax + 1), dmax + 1) (-1)++initVRev :: Int -> Int -> ST s (VSTArray s)+initVRev dmax xmax = do+ newArray (-(dmax + 1), dmax + 1) (xmax + 1)++-- 1 indexed, v[0] is used as a guard element+initVChanged :: Int -> ST s (BSTArray s)+initVChanged l = do+ a <- newArray (0, l) True+ writeArray a 0 False+ return a+ -- set to false for all lines which have a mapping later+ -- other lines are only present in one of the files++initH :: [Int32] -> HArray+initH a = listArray (0, length a) (0:a)++initM :: [Int] -> MapArray+initM a = listArray (0, length a) (0:a)++initP :: [B.ByteString] -> PArray+initP a = listArray (0, length a) (B.empty:a)++aLen :: (IArray a e) => a Int e -> Int+aLen a = snd $ bounds a+aLenM :: (MArray a e m) => a Int e -> m Int+aLenM a = getBounds a >>= return . snd++convertPatch :: Int -> PArray -> PArray -> (Int, Int, Int, Int)+ -> (Int,[B.ByteString],[B.ByteString])+convertPatch off a b (a0,a1,b0,b1)+ | b0 == b1 = (b0+off,getDelete a a0 a1,[])+ | a0 == a1 = (b0+off,[],getInsert b b0 b1)+ | otherwise = (b0+off,getDelete a a0 a1,getInsert b b0 b1)++getInsert :: PArray -> Int -> Int -> [B.ByteString]+getInsert b from to+ | from >= to = []+ | otherwise = (b!(from+1)):(getInsert b (from+1) to)+getDelete :: PArray -> Int -> Int -> [B.ByteString]+getDelete a from to+ | from >= to = []+ | otherwise = (a!(from+1)):(getDelete a (from+1) to)++createPatch :: BArray -> BArray -> [(Int, Int, Int, Int)]+createPatch c_a c_b =+ reverse $ createP c_a c_b (aLen c_a) (aLen c_b)++createP :: BArray -> BArray -> Int -> Int -> [(Int, Int, Int, Int)]+createP _ _ 0 0 = []+createP c_a c_b ia ib =+ if c_a!ia || c_b!ib+ then let ia' = skipChangedRev c_a ia+ ib' = skipChangedRev c_b ib+ in (ia',ia,ib',ib):(createP c_a c_b ia' ib')+ else createP c_a c_b (ia-1) (ib-1)++skipChangedRev :: BArray -> Int -> Int+skipChangedRev c i = if i >= 0 && c!i then skipChangedRev c (i-1) else i
@@ -0,0 +1,350 @@+-- Copyright (C) 2003 Peter Simons+-- Copyright (C) 2003,2008 David Roundy+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.++-- This module is intended to provide backwards-compatibility in the parsing+-- of darcs patches. In other words: don't change it, new features don't get+-- added here. The only user should be Darcs.Patch.Info.++module OldDate ( readUTCDate, showIsoDateTime ) where++import Text.ParserCombinators.Parsec+import System.Time+import Data.Char ( toUpper, isDigit )+import Control.Monad ( liftM, liftM2 )+import qualified Data.ByteString.Char8 as B+import Data.Maybe ( fromMaybe )++-- | Read/interpret a date string, assuming UTC if timezone+-- is not specified in the string+readUTCDate :: String -> CalendarTime+readUTCDate = readDate 0++readDate :: Int -> String -> CalendarTime+readDate tz d =+ case parseDate tz d of+ Left e -> error e+ Right ct -> ct++parseDate :: Int -> String -> Either String CalendarTime+parseDate tz d =+ if length d >= 14 && B.all isDigit bd+ then Right $+ CalendarTime (readI $ B.take 4 bd)+ (toEnum $ (+ (-1)) $ readI $ B.take 2 $ B.drop 4 bd)+ (readI $ B.take 2 $ B.drop 6 bd) -- Day+ (readI $ B.take 2 $ B.drop 8 bd) -- Hour+ (readI $ B.take 2 $ B.drop 10 bd) -- Minute+ (readI $ B.take 2 $ B.drop 12 bd) -- Second+ 0 Sunday 0 -- Picosecond, weekday and day of year unknown+ "GMT" 0 False+ else let dt = do { x <- date_time tz; eof; return x }+ in case parse dt "" d of+ Left e -> Left $ "bad date: "++d++" - "++show e+ Right ct -> Right ct+ where bd = B.pack (take 14 d)+ readI s = fst $ fromMaybe (error "parseDate: invalid date") (B.readInt s)++showIsoDateTime :: CalendarTime -> String+showIsoDateTime ct = concat [ show $ ctYear ct+ , twoDigit . show . (+1) . fromEnum $ ctMonth ct+ , twoDigit . show $ ctDay ct+ , twoDigit . show $ ctHour ct+ , twoDigit . show $ ctMin ct+ , twoDigit . show $ ctSec ct+ ]+ where twoDigit [] = undefined+ twoDigit x@(_:[]) = '0' : x+ twoDigit x@(_:_:[]) = x+ twoDigit _ = undefined++----- Parser Combinators ---------------------------------------------++-- |Case-insensitive variant of Parsec's 'char' function.++caseChar :: Char -> GenParser Char a Char+caseChar c = satisfy (\x -> toUpper x == toUpper c)++-- |Case-insensitive variant of Parsec's 'string' function.++caseString :: String -> GenParser Char a ()+caseString cs = mapM_ caseChar cs <?> cs++-- |Match a parser at least @n@ times.++manyN :: Int -> GenParser a b c -> GenParser a b [c]+manyN n p+ | n <= 0 = return []+ | otherwise = liftM2 (++) (count n p) (many p)++-- |Match a parser at least @n@ times, but no more than @m@ times.++manyNtoM :: Int -> Int -> GenParser a b c -> GenParser a b [c]+manyNtoM n m p+ | n < 0 = return []+ | n > m = return []+ | n == m = count n p+ | n == 0 = foldr (<|>) (return []) (map (\x -> try $ count x p) (reverse [1..m]))+ | otherwise = liftM2 (++) (count n p) (manyNtoM 0 (m-n) p)+++----- Date/Time Parser -----------------------------------------------++date_time :: Int -> CharParser a CalendarTime+date_time tz =+ choice [try $ cvs_date_time tz,+ try $ iso8601_date_time tz,+ old_date_time]++cvs_date_time :: Int -> CharParser a CalendarTime+cvs_date_time tz =+ do y <- year+ char '/'+ mon <- month_num + char '/'+ d <- day+ my_spaces+ h <- hour+ char ':'+ m <- minute+ char ':'+ s <- second+ z <- option tz $ my_spaces >> zone+ return (CalendarTime y mon d h m s 0 Monday 0 "" z False)++old_date_time :: CharParser a CalendarTime+old_date_time = do wd <- day_name+ my_spaces+ mon <- month_name+ my_spaces+ d <- day+ my_spaces+ h <- hour+ char ':'+ m <- minute+ char ':'+ s <- second+ my_spaces+ z <- zone+ my_spaces+ y <- year+ return (CalendarTime y mon d h m s 0 wd 0 "" z False)++{- FIXME: In case you ever want to use this outside of darcs, you should note + that this implementation of ISO 8601 is not complete. ++ reluctant to implement (ambiguous!): + * years > 9999 + * truncated representations with implied century (89 for 1989) + unimplemented: + * repeated durations (not relevant)+ * lowest order component fractions in intervals+ * negative dates (BC) + unverified or too relaxed:+ * the difference between 24h and 0h+ * allows stuff like 2005-1212; either you use the hyphen all the way + (2005-12-12) or you don't use it at all (20051212), but you don't use+ it halfway, likewise with time + * No bounds checking whatsoever on intervals! + (next action: read iso doc to see if bounds-checking required?) -}+iso8601_date_time :: Int -> CharParser a CalendarTime+iso8601_date_time localTz = try $ + do d <- iso8601_date+ t <- option id $ try $ do optional $ oneOf " T" + iso8601_time + return $ t $ d { ctTZ = localTz }++iso8601_date :: CharParser a CalendarTime+iso8601_date = + do d <- calendar_date <|> week_date <|> ordinal_date+ return $ foldr ($) nullCalendar d+ where + calendar_date = -- yyyy-mm-dd+ try $ do d <- optchain year_ [ (dash, month_), (dash, day_) ]+ -- allow other variants to be parsed correctly + notFollowedBy (digit <|> char 'W')+ return d+ week_date = --yyyy-Www-dd + try $ do yfn <- year_+ optional dash+ char 'W'+ -- offset human 'week 1' -> computer 'week 0'+ w' <- (\x -> x-1) `liftM` two_digits+ wd <- option 1 $ do { optional dash; n_digits 1 }+ let y = yfn nullCalendar+ firstDay = ctWDay y+ -- things that make this complicated+ -- 1. iso8601 weeks start from Monday; Haskell weeks start from Sunday+ -- 2. the first week is the one that contains at least Thursday+ -- if the year starts after Thursday, then some days of the year+ -- will have already passed before the first week+ let afterThursday = firstDay == Sunday || firstDay > Thursday+ w = if afterThursday then w'+1 else w'+ diff c = c { ctDay = (7 * w) + wd - (fromEnum firstDay) }+ return [(toUTCTime.toClockTime.diff.yfn)]+ ordinal_date = -- yyyy-ddd+ try $ optchain year_ [ (dash, yearDay_) ]+ --+ year_ = try $ do y <- four_digits <?> "year (0000-9999)"+ return $ \c -> c { ctYear = y }+ month_ = try $ do m <- two_digits <?> "month (1 to 12)"+ -- we (artificially) use ctPicosec to indicate+ -- whether the month has been specified.+ return $ \c -> c { ctMonth = intToMonth m, ctPicosec = 0 }+ day_ = try $ do d <- two_digits <?> "day in month (1 to 31)"+ return $ \c -> c { ctDay = d }+ yearDay_ = try $ do d <- n_digits 3 <?> "day in year (1 to 366)"+ return $ \c -> c { ctYDay = d }+ dash = char '-'++-- we return a function which sets the time on another calendar+iso8601_time :: CharParser a (CalendarTime -> CalendarTime)+iso8601_time = try $+ do ts <- optchain hour_ [ (colon , min_)+ , (colon , sec_)+ , (oneOf ",.", pico_) ] + z <- option id $ choice [ zulu , offset ]+ return $ foldr (.) id (z:ts)+ where + hour_ = do h <- two_digits+ return $ \c -> c { ctHour = h }+ min_ = do m <- two_digits+ return $ \c -> c { ctMin = m }+ sec_ = do s <- two_digits+ return $ \c -> c { ctSec = s }+ pico_ = do digs <- many digit+ let picoExp = 12+ digsExp = length digs+ let frac | null digs = 0+ | digsExp > picoExp = read $ take picoExp digs+ | otherwise = 10 ^ (picoExp - digsExp) * (read digs)+ return $ \c -> c { ctPicosec = frac }+ zulu = do { char 'Z'; return (\c -> c { ctTZ = 0 }) }+ offset = do sign <- choice [ do { char '+' >> return 1 }+ , do { char '-' >> return (-1) } ]+ h <- two_digits+ m <- option 0 $ do { optional colon; two_digits }+ return $ \c -> c { ctTZ = sign * 60 * ((h*60)+m) }+ colon = char ':'++optchain :: CharParser a b -> [(CharParser a c, CharParser a b)] -> CharParser a [b]+optchain p next = try $ + do r1 <- p+ r2 <- case next of + [] -> return []+ ((sep,p2):next2) -> option [] $ do { optional sep; optchain p2 next2 }+ return (r1:r2)++n_digits :: Int -> CharParser a Int +n_digits n = read `liftM` count n digit++two_digits, four_digits :: CharParser a Int+two_digits = n_digits 2+four_digits = n_digits 4++my_spaces :: CharParser a String+my_spaces = manyN 1 $ char ' '++day_name :: CharParser a Day+day_name = choice+ [ caseString "Mon" >> return Monday+ , try (caseString "Tue") >> return Tuesday+ , caseString "Wed" >> return Wednesday+ , caseString "Thu" >> return Thursday+ , caseString "Fri" >> return Friday+ , try (caseString "Sat") >> return Saturday+ , caseString "Sun" >> return Sunday+ ]++year :: CharParser a Int+year = four_digits++month_num :: CharParser a Month+month_num = do mn <- manyNtoM 1 2 digit + return $ intToMonth $ (read mn :: Int)++intToMonth :: Int -> Month+intToMonth 1 = January+intToMonth 2 = February+intToMonth 3 = March+intToMonth 4 = April+intToMonth 5 = May+intToMonth 6 = June+intToMonth 7 = July+intToMonth 8 = August+intToMonth 9 = September+intToMonth 10 = October+intToMonth 11 = November+intToMonth 12 = December+intToMonth _ = error "invalid month!"++month_name :: CharParser a Month+month_name = choice+ [ try (caseString "Jan") >> return January+ , caseString "Feb" >> return February+ , try (caseString "Mar") >> return March+ , try (caseString "Apr") >> return April+ , caseString "May" >> return May+ , try (caseString "Jun") >> return June+ , caseString "Jul" >> return July+ , caseString "Aug" >> return August+ , caseString "Sep" >> return September+ , caseString "Oct" >> return October+ , caseString "Nov" >> return November+ , caseString "Dec" >> return December+ ]++day :: CharParser a Int+day = do d <- manyNtoM 1 2 digit+ return (read d :: Int)++hour :: CharParser a Int+hour = two_digits++minute :: CharParser a Int+minute = two_digits++second :: CharParser a Int+second = two_digits++zone :: CharParser a Int+zone = choice+ [ do { char '+'; h <- hour; m <- minute; return (((h*60)+m)*60) }+ , do { char '-'; h <- hour; m <- minute; return (-((h*60)+m)*60) }+ , mkZone "UTC" 0+ , mkZone "UT" 0+ , mkZone "GMT" 0+ , mkZone "EST" (-5)+ , mkZone "EDT" (-4)+ , mkZone "CST" (-6)+ , mkZone "CDT" (-5)+ , mkZone "MST" (-7)+ , mkZone "MDT" (-6)+ , mkZone "PST" (-8)+ , mkZone "PDT" (-7)+ , mkZone "CEST" 2+ , mkZone "EEST" 3+ -- if we don't understand it, just give a GMT answer...+ , do { manyTill (oneOf $ ['a'..'z']++['A'..'Z']++[' '])+ (lookAhead space_digit);+ return 0 }+ ]+ where mkZone n o = try $ do { caseString n; return (o*60*60) }+ space_digit = try $ do { char ' '; oneOf ['0'..'9'] }++nullCalendar :: CalendarTime +nullCalendar = CalendarTime 0 January 0 0 0 0 1 Sunday 0 "" 0 False
@@ -0,0 +1,425 @@++A Document is at heart ShowS from the prelude+\htmladdnormallink{http://www.haskell.org/onlinereport/standard-prelude.html#\$tShowS}++Essentially, if you give a Doc a string it'll print out whatever it+wants followed by that string. So \verb!(text "foo")! makes the Doc that+prints \verb!"foo"! followed by its argument. The combinator names are taken+from Text.PrettyPrint.HughesPJ, although the behaviour of the two libraries is+slightly different.++The advantage of Printer over simple string appending/concatenating is+that the appends end up associating to the right, e.g.:++\begin{verbatim}+ (text "foo" <> text "bar") <> (text "baz" <> text "quux") ""+= \s -> (text "foo" <> text "bar") ((text "baz" <> text "quux") s) ""+= (text "foo" <> text "bar") ((text "baz" <> text "quux") "")+= (\s -> (text "foo") (text "bar" s)) ((text "baz" <> text "quux") "")+= text "foo" (text "bar" ((text "baz" <> text "quux") ""))+= (\s -> "foo" ++ s) (text "bar" ((text "baz" <> text "quux") ""))+= "foo" ++ (text "bar" ((text "baz" <> text "quux") ""))+= "foo" ++ ("bar" ++ ((text "baz" <> text "quux") ""))+= "foo" ++ ("bar" ++ ((\s -> text "baz" (text "quux" s)) ""))+= "foo" ++ ("bar" ++ (text "baz" (text "quux" "")))+= "foo" ++ ("bar" ++ ("baz" ++ (text "quux" "")))+= "foo" ++ ("bar" ++ ("baz" ++ ("quux" ++ "")))+\end{verbatim}++The Empty alternative comes in because you want+\begin{verbatim}+ text "a" $$ vcat xs $$ text "b"+\end{verbatim}+(\verb!$$! means ``above'', vcat is the list version of \verb!$$!) to be+\verb!"a\nb"! when \verb!xs! is \verb![]!, but without the concept of an+Empty Document each \verb!$$! would add a \verb!'\n'! and you'd end up with+\verb!"a\n\nb"!. Note that \verb!Empty /= text ""! (the latter would cause two+\verb!'\n'!s).++This code was made generic in the element type by Juliusz Chroboczek.+\begin{code}+module Printer (Printable(..), Doc(Doc,unDoc), Printers, Printers'(..), Printer, Color(..),+ hPutDoc, hPutDocLn, putDoc, putDocLn,+ hPutDocWith, hPutDocLnWith, putDocWith, putDocLnWith,+ renderString, renderStringWith, renderPS, renderPSWith,+ renderPSs, renderPSsWith, lineColor,+ prefix, insert_before_lastline, colorText, invisibleText, + hiddenText, hiddenPrefix, userchunk, text,+ printable, wrap_text,+ blueText, redText, greenText, magentaText, cyanText,+ unsafeText, unsafeBoth, unsafeBothText, unsafeChar,+ invisiblePS, packedString, unsafePackedString, userchunkPS,+ simplePrinters, invisiblePrinter, simplePrinter,+ doc, empty, (<>), (<?>), (<+>), ($$), vcat, vsep, hcat,+ minus, newline, plus, space, backslash, lparen, rparen,+ parens,+ errorDoc,+ ) where++import Data.List (intersperse)+import System.IO (Handle, stdout, hPutStr)+import ByteStringUtils ( linesPS )+import qualified Data.ByteString as B (ByteString, hPut, concat)+import qualified Data.ByteString.Char8 as BC (unpack, pack, singleton)++-- | A 'Printable' is either a String, a packed string, or a chunk of+-- text with both representations.+data Printable = S !String+ | PS !B.ByteString+ | Both !String !B.ByteString++-- | 'space_p' is the 'Printable' representation of a space.+space_p :: Printable+space_p = Both " " (BC.singleton ' ')++-- | 'newline_p' is the 'Printable' representation of a newline.+newline_p :: Printable+newline_p = S "\n"++-- | Minimal 'Doc's representing the common characters 'space', 'newline'+-- 'minus', 'plus', and 'backslash'.+space, newline, plus, minus, backslash :: Doc+space = unsafeBoth " " (BC.singleton ' ')+newline = unsafeChar '\n'+minus = unsafeBoth "-" (BC.singleton '-')+plus = unsafeBoth "+" (BC.singleton '+')+backslash = unsafeBoth "\\" (BC.singleton '\\')++-- | 'lparen' is the 'Doc' that represents @\"(\"@+lparen :: Doc+lparen = unsafeBoth "(" (BC.singleton '(')++-- | 'rparen' is the 'Doc' that represents @\")\"@+rparen :: Doc+rparen = unsafeBoth ")" (BC.singleton ')')++-- | @'parens' doc@ returns a 'Doc' with the content of @doc@ put within+-- a pair of parenthesis.+parens :: Doc -> Doc+parens d = lparen <> d <> rparen++errorDoc :: Doc -> a+errorDoc = error . renderStringWith simplePrinters'+++-- | 'putDocWith' puts a doc on stdout using the given printer.+putDocWith :: Printers -> Doc -> IO ()+putDocWith prs = hPutDocWith prs stdout++-- | 'putDocLnWith' puts a doc, followed by a newline on stdout using+-- the given printer.+putDocLnWith :: Printers -> Doc -> IO ()+putDocLnWith prs = hPutDocLnWith prs stdout+++-- | 'putDoc' puts a doc on stdout using the simple printer 'simplePrinters'.+putDoc :: Doc -> IO ()+-- | 'putDocLn' puts a doc, followed by a newline on stdout using+-- 'simplePrinters'+putDocLn :: Doc -> IO ()+putDoc = hPutDoc stdout+putDocLn = hPutDocLn stdout++-- | 'hputDocWith' puts a doc on the given handle using the given printer.+hPutDocWith :: Printers -> Handle -> Doc -> IO ()+-- | 'hputDocLnWith' puts a doc, followed by a newline on the given+-- handle using the given printer.+hPutDocLnWith :: Printers -> Handle -> Doc -> IO ()++hPutDocWith prs h d = hPrintPrintables h (renderWith (prs h) d)+hPutDocLnWith prs h d = hPutDocWith prs h (d <?> newline)++-- |'hputDoc' puts a doc on the given handle using 'simplePrinters'+hPutDoc :: Handle -> Doc -> IO ()+-- 'hputDocLn' puts a doc, followed by a newline on the given handle using+-- 'simplePrinters'.+hPutDocLn :: Handle -> Doc -> IO ()+hPutDoc = hPutDocWith simplePrinters+hPutDocLn = hPutDocLnWith simplePrinters++-- | @'hPrintPrintables' h@ prints a list of 'Printable's to the handle h+hPrintPrintables :: Handle -> [Printable] -> IO ()+hPrintPrintables h = mapM_ (hPrintPrintable h)++-- | @hPrintPrintable h@ prints a 'Printable' to the handle h.+hPrintPrintable :: Handle -> Printable -> IO ()+hPrintPrintable h (S ps) = hPutStr h ps+hPrintPrintable h (PS ps) = B.hPut h ps+hPrintPrintable h (Both _ ps) = B.hPut h ps++-- | a 'Doc' is a bit of enriched text. 'Doc's get concatanated using+-- '<>', which is right-associative.+newtype Doc = Doc { unDoc :: St -> Document }++-- | The State associated with a doc. Contains a set of printers for each+-- hanlde, and the current prefix of the document.+data St = St { printers :: !Printers',+ current_prefix :: !([Printable] -> [Printable]) }+type Printers = Handle -> Printers'++-- | A set of printers to print different types of text to a handle.+data Printers' = Printers {colorP :: !(Color -> Printer),+ invisibleP :: !Printer,+ hiddenP :: !Printer,+ userchunkP :: !Printer,+ defP :: !Printer,+ lineColorT :: !(Color -> Doc -> Doc),+ lineColorS :: !([Printable] -> [Printable])+ }+type Printer = Printable -> St -> Document++data Color = Blue | Red | Green | Cyan | Magenta++-- | 'Document' is a wrapper around '[Printable] -> [Printable]' which allows+-- for empty Documents. The simplest 'Documents' are built from 'String's+-- using 'text'.+data Document = Document ([Printable] -> [Printable])+ | Empty++-- | renders a 'Doc' into a 'String' with control codes for the+-- special features of the doc.+renderString :: Doc -> String+renderString = renderStringWith simplePrinters'++-- | renders a 'Doc' into a 'String' using a given set of printers.+renderStringWith :: Printers' -> Doc -> String+renderStringWith prs d = concatMap toString $ renderWith prs d+ where toString (S s) = s+ toString (PS ps) = BC.unpack ps+ toString (Both s _) = s++-- | renders a 'Doc' into 'B.ByteString' with control codes for the+-- special features of the Doc. See also 'readerString'.+renderPS :: Doc -> B.ByteString+renderPS = renderPSWith simplePrinters'++-- | renders a 'Doc' into a list of 'PackedStrings', one for each line.+renderPSs :: Doc -> [B.ByteString]+renderPSs = renderPSsWith simplePrinters'++-- | renders a doc into a 'B.ByteString' using a given set of printers.+renderPSWith :: Printers' -> Doc -> B.ByteString+renderPSWith prs d = B.concat $ renderPSsWith prs d++-- | renders a 'Doc' into a list of 'PackedStrings', one for each+-- chunk of text that was added to the doc, using the given set of+-- printers.+renderPSsWith :: Printers' -> Doc -> [B.ByteString]+renderPSsWith prs d = map toPS $ renderWith prs d+ where toPS (S s) = BC.pack s+ toPS (PS ps) = ps+ toPS (Both _ ps) = ps++-- | renders a 'Doc' into a list of 'Printables' using a set of+-- printers. Each item of the list corresponds to a string that was+-- added to the doc.+renderWith :: Printers' -> Doc -> [Printable]+renderWith ps (Doc d) = case d (init_state ps) of+ Empty -> []+ Document f -> f []++init_state :: Printers' -> St+init_state prs = St { printers = prs, current_prefix = id }++prefix :: String -> Doc -> Doc+prefix s (Doc d) = Doc $ \st ->+ let p = S s+ st' = st { current_prefix = current_prefix st . (p:) } in+ case d st' of+ Document d'' -> Document $ (p:) . d''+ Empty -> Empty+ ++insert_before_lastline :: Doc -> Doc -> Doc+insert_before_lastline a b =+ case reverse $ map packedString $ linesPS $ renderPS a of+ (ll:ls) -> vcat (reverse ls) $$ b $$ ll+ [] -> error "empty Doc given as first argument of Printer.insert_before_last_line"+++lineColor :: Color -> Doc -> Doc+lineColor c d = Doc $ \st -> case lineColorT (printers st) c d of+ Doc d' -> d' st++hiddenPrefix :: String -> Doc -> Doc+hiddenPrefix s (Doc d) =+ Doc $ \st -> let pr = printers st+ p = S (renderStringWith pr $ hiddenText s)+ st' = st { current_prefix = current_prefix st . (p:) }+ in case d st' of+ Document d'' -> Document $ (p:) . d''+ Empty -> Empty++-- | 'unsafeBoth' builds a Doc from a 'String' and a 'B.ByteString' representing+-- the same text, but does not check that they do.+unsafeBoth :: String -> B.ByteString -> Doc+unsafeBoth s ps = Doc $ simplePrinter (Both s ps)++-- | 'unsafeBothText' builds a 'Doc' from a 'String'. The string is stored in the+-- Doc as both a String and a 'B.ByteString'.+unsafeBothText :: String -> Doc+unsafeBothText s = Doc $ simplePrinter (Both s (BC.pack s))++-- | 'packedString' builds a 'Doc' from a 'B.ByteString' using 'printable'+packedString :: B.ByteString -> Doc+-- | 'unsafePackedString' builds a 'Doc' from a 'B.ByteString' using 'simplePrinter'+unsafePackedString :: B.ByteString -> Doc+-- | 'invisiblePS' creates a 'Doc' with invisible text from a 'B.ByteString'+invisiblePS :: B.ByteString -> Doc+-- | 'userchunkPS' creates a 'Doc' representing a user chunk from a 'B.ByteString'.+userchunkPS :: B.ByteString -> Doc+packedString = printable . PS+unsafePackedString = Doc . simplePrinter . PS+invisiblePS = invisiblePrintable . PS+userchunkPS = userchunkPrintable . PS++-- | 'unsafeChar' creates a Doc containing just one character.+unsafeChar :: Char -> Doc+unsafeChar = unsafeText . (:"")++-- | 'text' creates a 'Doc' from a @String@, using 'printable'.+text :: String -> Doc+-- | 'unsafeText' creates a 'Doc' from a 'String', using 'simplePrinter' directly+unsafeText :: String -> Doc+-- | 'invisibleText' creates a 'Doc' containing invisible text from a @String@+invisibleText :: String -> Doc+-- | 'hiddenText' creates a 'Doc' containing hidden text from a @String@+hiddenText :: String -> Doc+-- | 'userchunk' creates a 'Doc' containing a user chunk from a @String@+userchunk :: String -> Doc+-- | 'blueText' creates a 'Doc' containing blue text from a @String@+blueText, redText, greenText, magentaText, cyanText :: String -> Doc+text = printable . S+unsafeText = Doc . simplePrinter . S+invisibleText = invisiblePrintable . S+hiddenText = hiddenPrintable . S+userchunk = userchunkPrintable . S+blueText = colorText Blue+redText = colorText Red+greenText = colorText Green+magentaText = colorText Magenta+cyanText = colorText Cyan++-- | 'colorText' creates a 'Doc' containing colored text from a @String@+colorText :: Color -> String -> Doc+colorText c = mkColorPrintable c . S++-- | @'wrap_text' n s@ is a 'Doc' representing @s@ line-wrapped at 'n' characters+wrap_text :: Int -> String -> Doc+wrap_text n s =+ vcat $ map text $ reverse $ "": (foldl add_to_line [] $ words s)+ where add_to_line [] a = [a]+ add_to_line ("":d) a = (a:d)+ add_to_line (l:ls) new | length l + length new > n = new:l:ls+ add_to_line (l:ls) new = (l ++ " " ++ new):ls++-- | 'printable x' creates a 'Doc' from any 'Printable'.+printable, invisiblePrintable, hiddenPrintable, userchunkPrintable :: Printable -> Doc+printable x = Doc $ \st -> defP (printers st) x st++mkColorPrintable :: Color -> Printable -> Doc+mkColorPrintable c x = Doc $ \st -> colorP (printers st) c x st+invisiblePrintable x = Doc $ \st -> invisibleP (printers st) x st+hiddenPrintable x = Doc $ \st -> hiddenP (printers st) x st+userchunkPrintable x = Doc $ \st -> userchunkP (printers st) x st++-- | 'simplePrinters' is a 'Printers' which uses the set 'simplePriners\'' on any+-- handle.+simplePrinters :: Printers+simplePrinters _ = simplePrinters'++-- | A set of default printers suitable for any handle. Does not use color.+simplePrinters' :: Printers'+simplePrinters' = Printers { colorP = const simplePrinter,+ invisibleP = simplePrinter,+ hiddenP = invisiblePrinter,+ userchunkP = simplePrinter,+ defP = simplePrinter,+ lineColorT = const id,+ lineColorS = id+ }++-- | 'simplePrinter' is the simplest 'Printer': it just concatenates together+-- the pieces of the 'Doc'+simplePrinter :: Printer+-- | 'invisiblePrinter' is the 'Printer' for hidden text. It just replaces+-- the document with 'empty'. It's useful to have a printer that doesn't+-- actually do anything because this allows you to have tunable policies,+-- for example, only printing some text if it's to the terminal, but not+-- if it's to a file or vice-versa.+invisiblePrinter :: Printer+simplePrinter x = unDoc $ doc (\s -> x:s)+invisiblePrinter _ = unDoc empty++infixr 6 <>+infixr 6 <+>+infixr 5 $$++-- | The empty 'Doc'.+empty :: Doc+empty = Doc $ const Empty+doc :: ([Printable] -> [Printable]) -> Doc+doc f = Doc $ const $ Document f++-- | '(<>)' is the concatenation operator for 'Doc's+(<>) :: Doc -> Doc -> Doc+-- | @a '<?>' b@ is @a@ if it is not empty, else @b@.+(<?>) :: Doc -> Doc -> Doc+-- | @a '<+>' b@ is @a@ followed by a space, then @b@.+(<+>) :: Doc -> Doc -> Doc+-- | @a '$$' b@ is @a@ above @b@.+($$) :: Doc -> Doc -> Doc+-- a then b+Doc a <> Doc b =+ Doc $ \st -> case a st of+ Empty -> b st+ Document af ->+ Document (\s -> af $ case b st of+ Empty -> s+ Document bf -> bf s)++-- empty if a empty, else a then b+Doc a <?> Doc b =+ Doc $ \st -> case a st of+ Empty -> Empty+ Document af -> Document (\s -> af $ case b st of+ Empty -> s+ Document bf -> bf s)++-- a then space then b+Doc a <+> Doc b =+ Doc $ \st -> case a st of+ Empty -> b st+ Document af -> Document (\s -> af $ case b st of+ Empty -> s+ Document bf ->+ space_p:bf s)++-- a above b+Doc a $$ Doc b =+ Doc $ \st -> case a st of+ Empty -> b st+ Document af ->+ Document (\s -> af $ case b st of+ Empty -> s+ Document bf -> sf (newline_p:pf (bf s)))+ where pf = current_prefix st+ sf = lineColorS $ printers st++-- | 'vcat' piles vertically a list of 'Doc's.+vcat :: [Doc] -> Doc+vcat [] = empty+vcat ds = foldr1 ($$) ds++-- | 'vsep' piles vertically a list of 'Doc's leaving a blank line between each.+vsep :: [Doc] -> Doc+vsep [] = empty+vsep ds = foldr1 ($$) $ intersperse (text "") ds++-- | 'hcat' concatenates (horizontally) a list of 'Doc's+hcat :: [Doc] -> Doc+hcat [] = empty+hcat ds = foldr1 (<>) ds++\end{code}+
@@ -0,0 +1,202 @@+-- Various utility functions that do not belong anywhere else.++{-# OPTIONS_GHC -cpp #-}+{-# LANGUAGE CPP #-}++#include "gadts.h"++module Progress ( beginTedious, endTedious, tediousSize,+ debugMessage, debugFail, withoutProgress,+ progress, progressKeepLatest, finishedOne,+ finishedOneIO, progressList, minlist,+ setProgressMode) where++import Prelude hiding (lookup, catch)++import Control.Exception ( catch, throw )+import Control.Monad ( when )+import System.IO ( stdout, stderr, hFlush, hPutStr, hPutStrLn,+ hSetBuffering, hIsTerminalDevice,+ Handle, BufferMode(LineBuffering) )+import System.IO.Unsafe ( unsafePerformIO )+import Data.Char ( toLower )+import Data.Map ( Map, empty, adjust, insert, delete, lookup )+import Data.Maybe ( isJust )+import Control.Concurrent ( forkIO, threadDelay )+import Data.IORef ( IORef, newIORef, readIORef, writeIORef, modifyIORef )++import Darcs.Global ( withDebugMode, debugMessage, putTiming, debugFail )++handleProgress :: IO ()+handleProgress = do threadDelay 1000000+ handleMoreProgress "" 0++handleMoreProgress :: String -> Int -> IO ()+handleMoreProgress k n = withProgressMode $ \m ->+ if m then do s <- getProgressLast+ mp <- getProgressData s+ case mp of+ Nothing -> do threadDelay 1000000+ handleMoreProgress k n+ Just p -> do when (k /= s || n < sofar p) $ whenProgressMode $ printProgress s p+ threadDelay 1000000+ handleMoreProgress s (sofar p)+ else do threadDelay 1000000+ handleMoreProgress k n++printProgress :: String -> ProgressData -> IO ()+printProgress k (ProgressData {sofar=s, total=Just t, latest=Just l}) =+ myput (k++" "++show s++"/"++show t++" : "++l) (k++" "++show s++"/"++show t)+printProgress k (ProgressData {latest=Just l}) =+ myput (k++" "++l) k+printProgress k (ProgressData {sofar=s, total=Just t}) | t >= s =+ myput (k++" "++show s++"/"++show t) (k++" "++show s)+printProgress k (ProgressData {sofar=s}) =+ myput (k++" "++show s) k++myput :: String -> String -> IO ()+myput l s = withDebugMode $ \debugMode ->+ if debugMode+ then putTiming >> hPutStrLn stderr l+ else if '\n' `elem` l+ then myput (takeWhile (/= '\n') l) s+ else if length l < 80 then putTiming >> simpleput l+ else putTiming >> simpleput (take 80 s)++{-# NOINLINE simpleput #-}+simpleput :: String -> IO ()+simpleput = unsafePerformIO $ mkhPutCr stderr++-- | @beginTedious k@ starts a tedious process and registers it in+-- '_progressData' with the key @k@. A tedious process is one for which we+-- want a progress indicator.+--+-- Wouldn't it be safer if it had type String -> IO+-- ProgressDataKey, so that we can ensure there is no collision?+-- What happens if you call beginTedious twice with the same string, without+-- calling endTedious in the meantime?+beginTedious :: String -> IO ()+beginTedious k = do debugMessage $ "Beginning " ++ (map toLower k)+ setProgressData k $ ProgressData { sofar = 0,+ latest = Nothing,+ total = Nothing }++-- | @endTedious k@ unregisters the tedious process with key @k@, printing "Done" if such+-- a tedious process exists.+endTedious :: String -> IO ()+endTedious k = whenProgressMode $ do p <- getProgressData k+ modifyIORef _progressData (\(a,m) -> (a,delete k m))+ when (isJust p) $ debugMessage $ "Done "+++ (map toLower k)++tediousSize :: String -> Int -> IO ()+tediousSize k s = updateProgressData k uptot+ where uptot p = case total p of Just t -> seq ts $ p { total = Just ts }+ where ts = t + s+ Nothing -> p { total = Just s }++minlist :: Int+minlist = 4++progressList :: String -> [a] -> [a]+progressList _ [] = []+progressList k (x:xs) = if l < minlist then x:xs+ else startit x : pl xs+ where l = length (x:xs)+ startit y = unsafePerformIO $ do beginTedious k+ tediousSize k l+ return y+ pl [] = []+ pl [y] = unsafePerformIO $ do endTedious k+ return [y]+ pl (y:ys) = progress k y : pl ys+++progress :: String -> a -> a+progress k a = unsafePerformIO $ progressIO k >> return a++progressIO :: String -> IO ()+progressIO "" = return ()+progressIO k = do updateProgressData k (\p -> p { sofar = sofar p + 1,+ latest = Nothing })+ putDebug k ""++progressKeepLatest :: String -> a -> a+progressKeepLatest k a = unsafePerformIO $ progressKeepLatestIO k >> return a++progressKeepLatestIO :: String -> IO ()+progressKeepLatestIO "" = return ()+progressKeepLatestIO k = do updateProgressData k (\p -> p {sofar = sofar p + 1})+ putDebug k ""++finishedOne :: String -> String -> a -> a+finishedOne k l a = unsafePerformIO $ finishedOneIO k l >> return a++finishedOneIO :: String -> String -> IO ()+finishedOneIO "" _ = return ()+finishedOneIO k l = do updateProgressData k (\p -> p { sofar = sofar p + 1,+ latest = Just l })+ putDebug k l++putDebug :: String -> String -> IO ()+putDebug _ _ = return ()+--putDebug k "" = when (False && debugMode) $ hPutStrLn stderr $ "P: "++k+--putDebug k l = when (False && debugMode) $ hPutStrLn stderr $ "P: "++k++" : "++l++{-# NOINLINE _progressMode #-}+_progressMode :: IORef Bool+_progressMode = unsafePerformIO $ do hSetBuffering stderr LineBuffering+ newIORef True++{-# NOINLINE _progressData #-}+_progressData :: IORef (String, Map String ProgressData)+_progressData = unsafePerformIO $ do forkIO handleProgress+ newIORef ("", empty)++mkhPutCr :: Handle -> IO (String -> IO ())+mkhPutCr fe = do+ isTerm <- hIsTerminalDevice fe+ stdoutIsTerm <- hIsTerminalDevice stdout+ return $ if isTerm then \s -> do hPutStr fe $ '\r':s++"\r"+ hFlush fe+ let spaces = '\r':replicate (length s) ' '++"\r"+ hPutStr fe spaces+ when stdoutIsTerm $ hPutStr stdout spaces+ else \s -> when (not $ null s) $ do hPutStrLn fe s+ hFlush fe++setProgressMode :: Bool -> IO ()+setProgressMode m = writeIORef _progressMode m++withoutProgress :: IO a -> IO a+withoutProgress j = withProgressMode $ \m -> do debugMessage "Disabling progress reports..."+ setProgressMode False+ a <- j `catch` \e -> setProgressMode m >> throw e+ if m then debugMessage "Reenabling progress reports."+ else debugMessage "Leaving progress reports off."+ setProgressMode m+ return a++updateProgressData :: String -> (ProgressData -> ProgressData) -> IO ()+updateProgressData k f = whenProgressMode $ modifyIORef _progressData (\(_,m) -> (k,adjust f k m))++setProgressData :: String -> ProgressData -> IO ()+setProgressData k p = whenProgressMode $ modifyIORef _progressData (\(a,m) -> (a,insert k p m))++getProgressData :: String -> IO (Maybe ProgressData)+getProgressData k = withProgressMode $ \p -> if p then (lookup k . snd) `fmap` readIORef _progressData+ else return Nothing++getProgressLast :: IO String+getProgressLast = withProgressMode $ \p -> if p then fst `fmap` readIORef _progressData+ else return ""++whenProgressMode :: IO a -> IO ()+whenProgressMode j = withProgressMode $ const $ j >> return ()++withProgressMode :: (Bool -> IO a) -> IO a+withProgressMode j = readIORef _progressMode >>= j++data ProgressData = ProgressData { sofar :: !Int,+ latest :: !(Maybe String),+ total :: !(Maybe Int)}
@@ -0,0 +1,75 @@+-- Copyright (C) 2003 David Roundy+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.+++module RegChars ( regChars,+ ) where++(&&&) :: (a -> Bool) -> (a -> Bool) -> (a -> Bool)+(&&&) a b c = a c && b c++(|||) :: (a -> Bool) -> (a -> Bool) -> (a -> Bool)+(|||) a b c = a c || b c++{-# INLINE regChars #-}++-- | 'regChars' returns a filter function that tells if a char is a member+-- of the regChar expression or not. The regChar expression is basically a+-- set of chars, but it can contain ranges with use of the '-' (dash), and+-- it can also be specified as a complement set by prefixing with '^'+-- (caret). The dash and caret, as well as the backslash, can all be+-- escaped with a backslash to suppress their special meaning.+-- +-- NOTE: The '.' (dot) is allowed to be escaped. It has no special meaning+-- if it is not escaped, but the default 'filename_toks' in+-- Darcs.Commands.Replace uses an escaped dot (WHY?).++regChars :: String -> (Char -> Bool)+regChars ('^':cs) = not . normalRegChars (unescapeChars cs)+regChars ('\\':'^':cs) = normalRegChars $ unescapeChars $ '^':cs+regChars cs = normalRegChars $ unescapeChars cs++{-# INLINE unescapeChars #-}++-- | 'unescapeChars' unescapes whitespace, which is escaped in the replace+-- patch file format. It will also unescape escaped carets, which is useful+-- for escaping a leading caret that should not invert the regChars. All+-- other escapes are left for the unescaping in 'normalRegChars'.++unescapeChars :: String -> String+unescapeChars ('\\':'n':cs) = '\n' : unescapeChars cs+unescapeChars ('\\':'t':cs) = '\t' : unescapeChars cs+unescapeChars ('\\':'^':cs) = '^' : unescapeChars cs+unescapeChars (c:cs) = c : unescapeChars cs+unescapeChars [] = []++{-# INLINE normalRegChars #-}++-- | 'normalRegChars' assembles the filter function. It handles special+-- chars, and also unescaping of escaped special chars. If a non-special+-- char is still escaped by now we get a failure.++normalRegChars :: String -> (Char -> Bool)+normalRegChars ('\\':'.':cs) = (=='.') ||| normalRegChars cs+normalRegChars ('\\':'-':cs) = (=='-') ||| normalRegChars cs+normalRegChars ('\\':'\\':cs) = (=='\\') ||| normalRegChars cs+normalRegChars ('\\':c:_) = error $ "'\\"++[c]++"' not supported."+normalRegChars (c1:'-':c2:cs) = ((>= c1) &&& (<= c2)) ||| normalRegChars cs+normalRegChars (c:cs) = (== c) ||| normalRegChars cs+normalRegChars [] = \_ -> False++
@@ -0,0 +1,206 @@+-- Copyright (C) 2001, 2004 Ian Lynagh <igloo@earth.li>+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.++{-# OPTIONS_GHC -fno-warn-name-shadowing -cpp #-}+{-# LANGUAGE CPP #-}+++-- {-# OPTIONS_GHC -fglasgow-exts -fno-warn-name-shadowing #-}+-- -fglasgow-exts needed for nasty hack below+-- name shadowing disabled because a,b,c,d,e are shadowed loads in step 4+module SHA1 (sha1PS) where++import ByteStringUtils (unsafeWithInternals)+import qualified Data.ByteString as B (ByteString, pack, length, concat)++import Data.Char (intToDigit)+import Data.Bits (xor, (.&.), (.|.), complement, rotateL, shiftL, shiftR)+import Data.Word (Word8, Word32)+import Foreign.Ptr (Ptr, castPtr)+import Foreign.Marshal.Array (advancePtr)+import Foreign.Storable (peek, poke)+import System.IO.Unsafe (unsafePerformIO)++data ABCDE = ABCDE !Word32 !Word32 !Word32 !Word32 !Word32+data XYZ = XYZ !Word32 !Word32 !Word32++sha1PS :: B.ByteString -> String+sha1PS s = s5+ where s1_2 = sha1_step_1_2_pad_length s+ abcde = sha1_step_3_init+ abcde' = unsafePerformIO+ $ unsafeWithInternals s1_2 (\ptr len ->+ do let ptr' = castPtr ptr+#ifndef BIGENDIAN+ fiddle_endianness ptr' len+#endif+ sha1_step_4_main abcde ptr' len)+ s5 = sha1_step_5_display abcde'++fiddle_endianness :: Ptr Word32 -> Int -> IO ()+fiddle_endianness p 0 = p `seq` return ()+fiddle_endianness p n+ = do x <- peek p+ poke p $ shiftL x 24+ .|. shiftL (x .&. 0xff00) 8+ .|. (shiftR x 8 .&. 0xff00)+ .|. shiftR x 24+ fiddle_endianness (p `advancePtr` 1) (n - 4)++-- sha1_step_1_2_pad_length assumes the length is at most 2^61.+-- This seems reasonable as the Int used to represent it is normally 32bit,+-- but obviously could go wrong with large inputs on 64bit machines.+-- The B.ByteString library should probably move to Word64s if this is an+-- issue, though.++sha1_step_1_2_pad_length :: B.ByteString -> B.ByteString+sha1_step_1_2_pad_length s+ = let len = B.length s+ num_nuls = (55 - len) `mod` 64+ padding = 128:replicate num_nuls 0+ len_w8s = reverse $ size_split 8 (fromIntegral len*8)+ in B.concat [s, B.pack padding, B.pack len_w8s]++size_split :: Int -> Integer -> [Word8]+size_split 0 _ = []+size_split p n = fromIntegral d:size_split (p-1) n'+ where (n', d) = divMod n 256++sha1_step_3_init :: ABCDE+sha1_step_3_init = ABCDE 0x67452301 0xefcdab89 0x98badcfe 0x10325476 0xc3d2e1f0++sha1_step_4_main :: ABCDE -> Ptr Word32 -> Int -> IO ABCDE+sha1_step_4_main abcde _ 0 = return $! abcde+sha1_step_4_main (ABCDE a0@a b0@b c0@c d0@d e0@e) s len+ = do+ (e, b) <- doit f1 0x5a827999 (x 0) a b c d e+ (d, a) <- doit f1 0x5a827999 (x 1) e a b c d+ (c, e) <- doit f1 0x5a827999 (x 2) d e a b c+ (b, d) <- doit f1 0x5a827999 (x 3) c d e a b+ (a, c) <- doit f1 0x5a827999 (x 4) b c d e a+ (e, b) <- doit f1 0x5a827999 (x 5) a b c d e+ (d, a) <- doit f1 0x5a827999 (x 6) e a b c d+ (c, e) <- doit f1 0x5a827999 (x 7) d e a b c+ (b, d) <- doit f1 0x5a827999 (x 8) c d e a b+ (a, c) <- doit f1 0x5a827999 (x 9) b c d e a+ (e, b) <- doit f1 0x5a827999 (x 10) a b c d e+ (d, a) <- doit f1 0x5a827999 (x 11) e a b c d+ (c, e) <- doit f1 0x5a827999 (x 12) d e a b c+ (b, d) <- doit f1 0x5a827999 (x 13) c d e a b+ (a, c) <- doit f1 0x5a827999 (x 14) b c d e a+ (e, b) <- doit f1 0x5a827999 (x 15) a b c d e+ (d, a) <- doit f1 0x5a827999 (m 16) e a b c d+ (c, e) <- doit f1 0x5a827999 (m 17) d e a b c+ (b, d) <- doit f1 0x5a827999 (m 18) c d e a b+ (a, c) <- doit f1 0x5a827999 (m 19) b c d e a+ (e, b) <- doit f2 0x6ed9eba1 (m 20) a b c d e+ (d, a) <- doit f2 0x6ed9eba1 (m 21) e a b c d+ (c, e) <- doit f2 0x6ed9eba1 (m 22) d e a b c+ (b, d) <- doit f2 0x6ed9eba1 (m 23) c d e a b+ (a, c) <- doit f2 0x6ed9eba1 (m 24) b c d e a+ (e, b) <- doit f2 0x6ed9eba1 (m 25) a b c d e+ (d, a) <- doit f2 0x6ed9eba1 (m 26) e a b c d+ (c, e) <- doit f2 0x6ed9eba1 (m 27) d e a b c+ (b, d) <- doit f2 0x6ed9eba1 (m 28) c d e a b+ (a, c) <- doit f2 0x6ed9eba1 (m 29) b c d e a+ (e, b) <- doit f2 0x6ed9eba1 (m 30) a b c d e+ (d, a) <- doit f2 0x6ed9eba1 (m 31) e a b c d+ (c, e) <- doit f2 0x6ed9eba1 (m 32) d e a b c+ (b, d) <- doit f2 0x6ed9eba1 (m 33) c d e a b+ (a, c) <- doit f2 0x6ed9eba1 (m 34) b c d e a+ (e, b) <- doit f2 0x6ed9eba1 (m 35) a b c d e+ (d, a) <- doit f2 0x6ed9eba1 (m 36) e a b c d+ (c, e) <- doit f2 0x6ed9eba1 (m 37) d e a b c+ (b, d) <- doit f2 0x6ed9eba1 (m 38) c d e a b+ (a, c) <- doit f2 0x6ed9eba1 (m 39) b c d e a+ (e, b) <- doit f3 0x8f1bbcdc (m 40) a b c d e+ (d, a) <- doit f3 0x8f1bbcdc (m 41) e a b c d+ (c, e) <- doit f3 0x8f1bbcdc (m 42) d e a b c+ (b, d) <- doit f3 0x8f1bbcdc (m 43) c d e a b+ (a, c) <- doit f3 0x8f1bbcdc (m 44) b c d e a+ (e, b) <- doit f3 0x8f1bbcdc (m 45) a b c d e+ (d, a) <- doit f3 0x8f1bbcdc (m 46) e a b c d+ (c, e) <- doit f3 0x8f1bbcdc (m 47) d e a b c+ (b, d) <- doit f3 0x8f1bbcdc (m 48) c d e a b+ (a, c) <- doit f3 0x8f1bbcdc (m 49) b c d e a+ (e, b) <- doit f3 0x8f1bbcdc (m 50) a b c d e+ (d, a) <- doit f3 0x8f1bbcdc (m 51) e a b c d+ (c, e) <- doit f3 0x8f1bbcdc (m 52) d e a b c+ (b, d) <- doit f3 0x8f1bbcdc (m 53) c d e a b+ (a, c) <- doit f3 0x8f1bbcdc (m 54) b c d e a+ (e, b) <- doit f3 0x8f1bbcdc (m 55) a b c d e+ (d, a) <- doit f3 0x8f1bbcdc (m 56) e a b c d+ (c, e) <- doit f3 0x8f1bbcdc (m 57) d e a b c+ (b, d) <- doit f3 0x8f1bbcdc (m 58) c d e a b+ (a, c) <- doit f3 0x8f1bbcdc (m 59) b c d e a+ (e, b) <- doit f2 0xca62c1d6 (m 60) a b c d e+ (d, a) <- doit f2 0xca62c1d6 (m 61) e a b c d+ (c, e) <- doit f2 0xca62c1d6 (m 62) d e a b c+ (b, d) <- doit f2 0xca62c1d6 (m 63) c d e a b+ (a, c) <- doit f2 0xca62c1d6 (m 64) b c d e a+ (e, b) <- doit f2 0xca62c1d6 (m 65) a b c d e+ (d, a) <- doit f2 0xca62c1d6 (m 66) e a b c d+ (c, e) <- doit f2 0xca62c1d6 (m 67) d e a b c+ (b, d) <- doit f2 0xca62c1d6 (m 68) c d e a b+ (a, c) <- doit f2 0xca62c1d6 (m 69) b c d e a+ (e, b) <- doit f2 0xca62c1d6 (m 70) a b c d e+ (d, a) <- doit f2 0xca62c1d6 (m 71) e a b c d+ (c, e) <- doit f2 0xca62c1d6 (m 72) d e a b c+ (b, d) <- doit f2 0xca62c1d6 (m 73) c d e a b+ (a, c) <- doit f2 0xca62c1d6 (m 74) b c d e a+ (e, b) <- doit f2 0xca62c1d6 (m 75) a b c d e+ (d, a) <- doit f2 0xca62c1d6 (m 76) e a b c d+ (c, e) <- doit f2 0xca62c1d6 (m 77) d e a b c+ (b, d) <- doit f2 0xca62c1d6 (m 78) c d e a b+ (a, c) <- doit f2 0xca62c1d6 (m 79) b c d e a+ let abcde' = ABCDE (a0 + a) (b0 + b) (c0 + c) (d0 + d) (e0 + e)+ sha1_step_4_main abcde' (s `advancePtr` 16) (len - 64)+ where {-# INLINE f1 #-}+ f1 (XYZ x y z) = (x .&. y) .|. ((complement x) .&. z)+ {-# INLINE f2 #-}+ f2 (XYZ x y z) = x `xor` y `xor` z+ {-# INLINE f3 #-}+ f3 (XYZ x y z) = (x .&. y) .|. (x .&. z) .|. (y .&. z)+ {-# INLINE x #-}+ x n = peek (s `advancePtr` n)+ {-# INLINE m #-}+ m n = do let base = s `advancePtr` (n .&. 15)+ x0 <- peek base+ x1 <- peek (s `advancePtr` ((n - 14) .&. 15))+ x2 <- peek (s `advancePtr` ((n - 8) .&. 15))+ x3 <- peek (s `advancePtr` ((n - 3) .&. 15))+ let res = rotateL (x0 `xor` x1 `xor` x2 `xor` x3) 1+ poke base res+ return res+ {-# INLINE doit #-}+ doit f k i a b c d e = a `seq` c `seq`+ do i' <- i+ return (rotateL a 5 + f (XYZ b c d) + e + i' + k,+ rotateL b 30)++sha1_step_5_display :: ABCDE -> String+sha1_step_5_display (ABCDE a b c d e)+ = concatMap showAsHex [a, b, c, d, e]++showAsHex :: Word32 -> String+showAsHex n = showIt 8 n ""+ where+ showIt :: Int -> Word32 -> String -> String+ showIt 0 _ r = r+ showIt i x r = case quotRem x 16 of+ (y, z) -> let c = intToDigit (fromIntegral z)+ in c `seq` showIt (i-1) y (c:r)
@@ -0,0 +1,321 @@+{-# LANGUAGE CPP, ForeignFunctionInterface #-}++module Ssh ( grabSSH, runSSH, getSSH, copySSH, copySSHs, SSHCmd(..),+ environmentHelpSsh, environmentHelpScp, environmentHelpSshPort+ ) where++import Prelude hiding ( lookup, catch )++import System.Exit ( ExitCode(..) )+import System.Environment ( getEnv )+#ifndef WIN32+import System.Posix.Process ( getProcessID )+#else+import Darcs.Utils ( showHexLen )+import Data.Bits ( (.&.) )+import System.Random ( randomIO )+#endif+import System.IO ( Handle, hPutStr, hPutStrLn, hGetLine, hGetContents, hClose, hFlush )+import System.IO.Unsafe ( unsafePerformIO )+import System.Directory ( doesFileExist, createDirectoryIfMissing )+import Control.Monad ( when )+import System.Process ( runInteractiveProcess )++import Data.Map ( Map, empty, insert, lookup )+import Data.IORef ( IORef, newIORef, readIORef, modifyIORef )++import Darcs.SignalHandler ( catchNonSignal )+import Darcs.Utils ( withCurrentDirectory, breakCommand, prettyException, catchall )+import Darcs.Global ( atexit, sshControlMasterDisabled, darcsdir, withDebugMode )+import Darcs.Lock ( withTemp, withOpenTemp, tempdir_loc, removeFileMayNotExist )+import Exec ( exec, Redirects, Redirect(..), )+import Progress ( withoutProgress, debugMessage, debugFail, progressList )++import qualified Data.ByteString as B (ByteString, hGet, writeFile, readFile)+import qualified Data.ByteString.Char8 as BC (unpack)++#include "impossible.h"++{-# NOINLINE sshConnections #-}+sshConnections :: IORef (Map String (Maybe Connection))+sshConnections = unsafePerformIO $ newIORef empty++data Connection = C { inp :: !Handle, out :: !Handle, err :: !Handle, deb :: String -> IO () }++withSSHConnection :: String -> (Connection -> IO a) -> IO a -> IO a+withSSHConnection x withconnection withoutconnection =+ withoutProgress $+ do cs <- readIORef sshConnections+ let uhost = takeWhile (/= ':') x+ url = cleanrepourl x+ case lookup url (cs :: Map String (Maybe Connection)) of+ Just Nothing -> withoutconnection+ Just (Just c) -> withconnection c+ Nothing ->+ do mc <- do (ssh,sshargs_) <- getSSHOnly SSH+ let sshargs = sshargs_ ++ [uhost,"darcs","transfer-mode","--repodir",cleanrepodir x]+ debugMessage $ "ssh "++unwords sshargs+ (i,o,e,_) <- runInteractiveProcess ssh sshargs Nothing Nothing+ l <- hGetLine o+ if l == "Hello user, I am darcs transfer mode"+ then return ()+ else debugFail "Couldn't start darcs transfer-mode on server"+ let c = C { inp = i, out = o, err = e,+ deb = \s -> debugMessage ("with ssh (transfer-mode) "++uhost++": "++s) }+ modifyIORef sshConnections (insert url (Just c))+ return $ Just c+ `catchNonSignal`+ \e -> do debugMessage $ "Failed to start ssh connection:\n "+++ prettyException e+ severSSHConnection x+ debugMessage $ unlines $+ [ "NOTE: the server may be running a version of darcs prior to 2.0.0."+ , ""+ , "Installing darcs 2 on the server will speed up ssh-based commands."+ ]+ return Nothing+ maybe withoutconnection withconnection mc++severSSHConnection :: String -> IO ()+severSSHConnection x = do debugMessage $ "Severing ssh failed connection to "++x+ modifyIORef sshConnections (insert (cleanrepourl x) Nothing)++cleanrepourl :: String -> String+cleanrepourl zzz | take (length dd) zzz == dd = ""+ where dd = darcsdir++"/"+cleanrepourl (z:zs) = z : cleanrepourl zs+cleanrepourl "" = ""++cleanrepodir :: String -> String+cleanrepodir = cleanrepourl . drop 1 . dropWhile (/= ':')++grabSSH :: String -> Connection -> IO B.ByteString+grabSSH x c = do+ let dir = drop 1 $ dropWhile (/= ':') x+ dd = darcsdir++"/"+ clean zzz | take (length dd) zzz == dd = drop (length dd) zzz+ clean (_:zs) = clean zs+ clean "" = bug $ "Buggy path in grabSSH: "++x+ file = clean dir+ failwith e = do severSSHConnection x+ eee <- hGetContents (err c) -- ratify hGetContents: it's okay+ -- here because we're only grabbing+ -- stderr, and we're also about to+ -- throw the contents.+ debugFail $ e ++ " grabbing ssh file "++x++"\n"++eee+ deb c $ "get "++file+ hPutStrLn (inp c) $ "get " ++ file+ hFlush (inp c)+ l2 <- hGetLine (out c)+ if l2 == "got "++file+ then do showlen <- hGetLine (out c)+ case reads showlen of+ [(len,"")] -> B.hGet (out c) len+ _ -> failwith "Couldn't get length"+ else if l2 == "error "++file+ then do e <- hGetLine (out c)+ case reads e of+ (msg,_):_ -> debugFail $ "Error reading file remotely:\n"++msg+ [] -> failwith "An error occurred"+ else failwith "Error"++sshStdErrMode :: IO Redirect+sshStdErrMode = withDebugMode $ \amdebugging ->+ return $ if amdebugging then AsIs else Null++copySSH :: String -> FilePath -> IO ()+copySSH uRaw f = withSSHConnection uRaw (\c -> grabSSH uRaw c >>= B.writeFile f) $+ do let u = escape_dollar uRaw+ stderr_behavior <- sshStdErrMode+ r <- runSSH SCP u [] [u,f] (AsIs,AsIs,stderr_behavior)+ when (r /= ExitSuccess) $+ debugFail $ "(scp) failed to fetch: " ++ u+ where {- '$' in filenames is troublesome for scp, for some reason.. -}+ escape_dollar :: String -> String+ escape_dollar = concatMap tr+ where tr '$' = "\\$"+ tr c = [c]++copySSHs :: String -> [String] -> FilePath -> IO ()+copySSHs u ns d =+ withSSHConnection u (\c -> withCurrentDirectory d $+ mapM_ (\n -> grabSSH (u++"/"++n) c >>= B.writeFile n) $+ progressList "Copying via ssh" ns) $+ do let path = drop 1 $ dropWhile (/= ':') u+ host = takeWhile (/= ':') u+ cd = "cd "++path++"\n"+ input = cd++(unlines $ map ("get "++) ns)+ withCurrentDirectory d $ withOpenTemp $ \(th,tn) ->+ withTemp $ \sftpoutput ->+ do hPutStr th input+ hClose th+ stderr_behavior <- sshStdErrMode+ r <- runSSH SFTP u [] [host] (File tn, File sftpoutput, stderr_behavior)+ let files = if length ns > 5+ then (take 5 ns) ++ ["and "+ ++ (show (length ns - 5)) ++ " more"]+ else ns+ hint = if take 1 path == "~"+ then ["sftp doesn't expand ~, use path/ instead of ~/path/"]+ else []+ when (r /= ExitSuccess) $ do+ outputPS <- B.readFile sftpoutput+ debugFail $ unlines $+ ["(sftp) failed to fetch files.",+ "source directory: " ++ path,+ "source files:"] ++ files +++ ["sftp output:",BC.unpack outputPS] +++ hint++-- ---------------------------------------------------------------------+-- older ssh helper functions+-- ---------------------------------------------------------------------++data SSHCmd = SSH | SCP | SFTP++instance Show SSHCmd where+ show SSH = "ssh"+ show SCP = "scp"+ show SFTP = "sftp"++runSSH :: SSHCmd -> String -> [String] -> [String] -> Redirects -> IO ExitCode+runSSH cmd remoteAddr preArgs postArgs redirs =+ do (ssh, args) <- getSSH cmd remoteAddr+ exec ssh (preArgs ++ args ++ postArgs) redirs++-- | Return the command and arguments needed to run an ssh command+-- along with any extra features like use of the control master.+-- See 'getSSHOnly'+getSSH :: SSHCmd -> String -- ^ remote path+ -> IO (String, [String])+getSSH cmd remoteAddr =+ do (ssh, ssh_args) <- getSSHOnly cmd+ cm_args <- if sshControlMasterDisabled+ then return []+ else do -- control master+ cmPath <- controlMasterPath remoteAddr+ hasLaunchedCm <- doesFileExist cmPath+ when (not hasLaunchedCm) $ launchSSHControlMaster remoteAddr+ hasCmFeature <- doesFileExist cmPath+ return $ if hasCmFeature then [ "-o ControlPath=" ++ cmPath ] else []+ let verbosity = case cmd of+ SCP -> ["-q"] -- (p)scp is the only one that recognises -q+ -- sftp and (p)sftp do not, and plink neither+ _ -> []+ --+ return (ssh, verbosity ++ ssh_args ++ cm_args)++-- | Return the command and arguments needed to run an ssh command.+-- First try the appropriate darcs environment variable and SSH_PORT+-- defaulting to "ssh" and no specified port.+getSSHOnly :: SSHCmd -> IO (String, [String])+getSSHOnly cmd =+ do ssh_command <- getEnv (evar cmd) `catchall` return (show cmd)+ -- port+ port <- (portFlag cmd `fmap` getEnv "SSH_PORT") `catchall` return []+ let (ssh, ssh_args) = breakCommand ssh_command+ --+ return (ssh, ssh_args ++ port)+ where+ evar SSH = "DARCS_SSH"+ evar SCP = "DARCS_SCP"+ evar SFTP = "DARCS_SFTP"+ portFlag SSH x = ["-p", x]+ portFlag SCP x = ["-P", x]+ portFlag SFTP x = ["-oPort="++x]++environmentHelpSsh :: ([String], [String])+environmentHelpSsh = (["DARCS_SSH"], [+ "Repositories of the form [user@]host:[dir] are taken to be remote",+ "repositories, which Darcs accesses with the external program ssh(1).",+ "",+ "The environment variable $DARCS_SSH can be used to specify an",+ "alternative SSH client. Arguments may be included, separated by",+ "whitespace. The value is not interpreted by a shell, so shell",+ "constructs cannot be used; in particular, it is not possible for the",+ "program name to contain whitespace by using quoting or escaping."])++environmentHelpScp :: ([String], [String])+environmentHelpScp = (["DARCS_SCP", "DARCS_SFTP"], [+ "When reading from a remote repository, Darcs will attempt to run",+ "`darcs transfer-mode' on the remote host. This will fail if the",+ "remote host only has Darcs 1 installed, doesn't have Darcs installed",+ "at all, or only allows SFTP.",+ "",+ "If transfer-mode fails, Darcs will fall back on scp(1) and sftp(1).",+ "The commands invoked can be customized with the environment variables",+ "$DARCS_SCP and $DARCS_SFTP respectively, which behave like $DARCS_SSH.",+ "If the remote end allows only sftp, try setting DARCS_SCP=sftp."])++environmentHelpSshPort :: ([String], [String])+environmentHelpSshPort = (["SSH_PORT"], [+ "If this environment variable is set, it will be used as the port",+ "number for all SSH calls made by Darcs (when accessing remote",+ "repositories over SSH). This is useful if your SSH server does not",+ "run on the default port, and your SSH client does not support",+ "ssh_config(5). OpenSSH users will probably prefer to put something",+ "like `Host *.example.net Port 443' into their ~/.ssh/config file."])++-- | Return True if this version of ssh has a ControlMaster feature+-- The ControlMaster functionality allows for ssh multiplexing+hasSSHControlMaster :: Bool+hasSSHControlMaster = unsafePerformIO hasSSHControlMasterIO++-- Because of the unsafePerformIO above, this can be called at any+-- point. It cannot rely on any state, not even the current directory.+hasSSHControlMasterIO :: IO Bool+hasSSHControlMasterIO = do+ (ssh, _) <- getSSHOnly SSH+ -- If ssh has the ControlMaster feature, it will recognise the+ -- the -O flag, but exit with status 255 because of the nonsense+ -- command. If it does not have the feature, it will simply dump+ -- a help message on the screen and exit with 1.+ sx <- exec ssh ["-O", "an_invalid_command"] (Null,Null,Null)+ case sx of+ ExitFailure 255 -> return True+ _ -> return False++-- | Launch an SSH control master in the background, if available.+-- We don't have to wait for it or anything.+-- Note also that this will cleanup after itself when darcs exits+launchSSHControlMaster :: String -> IO ()+launchSSHControlMaster rawAddr =+ when hasSSHControlMaster $ do+ let addr = takeWhile (/= ':') rawAddr+ (ssh, ssh_args) <- getSSHOnly SSH+ cmPath <- controlMasterPath addr+ removeFileMayNotExist cmPath+ -- -f : put ssh in the background once it succeeds in logging you in+ -- -M : launch as the control master for addr+ -- -N : don't run any commands+ -- -S : use cmPath as the ControlPath. Equivalent to -oControlPath=+ exec ssh (ssh_args ++ [addr, "-S", cmPath, "-N", "-f", "-M"]) (Null,Null,AsIs)+ atexit $ exitSSHControlMaster addr+ return ()++-- | Tell the SSH control master for a given path to exit.+exitSSHControlMaster :: String -> IO ()+exitSSHControlMaster addr = do+ (ssh, ssh_args) <- getSSHOnly SSH+ cmPath <- controlMasterPath addr+ exec ssh (ssh_args ++ [addr, "-S", cmPath, "-O", "exit"]) (Null,Null,Null)+ return ()++-- | Create the directory ssh control master path for a given address+controlMasterPath :: String -- ^ remote path (foo\@bar.com:file is ok; the file part with be stripped)+ -> IO FilePath+controlMasterPath rawAddr = do+ let addr = takeWhile (/= ':') rawAddr+ tmp <- (fmap (/// ".darcs") $ getEnv "HOME") `catchall` tempdir_loc+#ifdef WIN32+ r <- randomIO+ let suffix = (showHexLen 6 (r .&. 0xFFFFFF :: Int))+#else+ suffix <- show `fmap` getProcessID+#endif+ let tmpDarcsSsh = tmp /// "darcs-ssh"+ createDirectoryIfMissing True tmpDarcsSsh+ return $ tmpDarcsSsh /// addr ++ suffix++(///) :: FilePath -> FilePath -> FilePath+d /// f = d ++ "/" ++ f
@@ -0,0 +1,13 @@+{-# LANGUAGE CPP #-}+{-# OPTIONS -cpp #-}++module ThisVersion ( darcs_version ) where++#ifndef PACKAGE_VERSION+#define PACKAGE_VERSION "0"+#define PACKAGE_VERSION_STATE "no version info"+#endif++{-# NOINLINE darcs_version #-}+darcs_version :: String+darcs_version = PACKAGE_VERSION ++ " (" ++ PACKAGE_VERSION_STATE ++ ")"
@@ -0,0 +1,370 @@+{-# LANGUAGE CPP, ForeignFunctionInterface #-}++module URL ( copyUrl, copyUrlFirst, pipeliningEnabledByDefault,+ setDebugHTTP, setHTTPPipelining, waitUrl,+ Cachable(Cachable, Uncachable, MaxAge),+ environmentHelpProxy, environmentHelpProxyPassword+ ) where++import Data.IORef ( newIORef, readIORef, writeIORef, IORef )+import Data.Map ( Map )+import Data.List ( delete )+import qualified Data.Map as Map+import System.Directory ( copyFile )+import System.IO.Unsafe ( unsafePerformIO )+import Control.Concurrent ( forkIO )+import Control.Concurrent.Chan ( isEmptyChan, newChan, readChan, writeChan, Chan )+import Control.Concurrent.MVar ( isEmptyMVar, modifyMVar_, newEmptyMVar, newMVar, putMVar, readMVar, withMVar, MVar )+import Control.Monad ( unless, when )+import Control.Monad.Trans ( liftIO )+import Control.Monad.State ( evalStateT, get, modify, put, StateT )+import Foreign.C.Types ( CInt )++import Workaround ( renameFile )+import Darcs.Global ( atexit )+import Progress ( debugFail, debugMessage )+import Darcs.Lock ( removeFileMayNotExist )++import Numeric ( showHex )+import System.Random ( randomRIO )++#ifdef HAVE_CURL+import Foreign.C.String ( withCString, peekCString, CString )+#else+import qualified HTTP ( request_url, wait_next_url )+#endif+#include "impossible.h"++data UrlRequest = UrlRequest { url :: String+ , file :: FilePath+ , cachable :: Cachable+ , priority :: Priority+ , notifyVar :: MVar String }++data Cachable = Cachable | Uncachable | MaxAge !CInt+ deriving (Show, Eq)++data UrlState = UrlState { inProgress :: Map String ( FilePath+ , [FilePath]+ , Cachable+ , (MVar String) )+ , waitToStart :: Q String+ , pipeLength :: Int+ , randomJunk :: String }++data Q a = Q [a] [a]++readQ :: Q a -> Maybe (a, Q a)+readQ (Q (x:xs) ys) = Just (x, Q xs ys)+readQ (Q [] ys) = do x:xs <- Just $ reverse ys+ Just (x, Q xs [])++insertQ :: a -> Q a -> Q a+insertQ y (Q xs ys) = Q xs (y:ys)++pushQ :: a -> Q a -> Q a+pushQ x (Q xs ys) = Q (x:xs) ys++deleteQ :: Eq a => a -> Q a -> Q a+deleteQ x (Q xs ys) = Q (delete x xs) (delete x ys)++elemQ :: Eq a => a -> Q a -> Bool+elemQ x (Q xs ys) = x `elem` xs || x `elem` ys++emptyQ :: Q a+emptyQ = Q [] []++nullQ :: Q a -> Bool+nullQ (Q [] []) = True+nullQ _ = False++data Priority = High | Low deriving Eq++#if defined(CURL_PIPELINING) || defined(CURL_PIPELINING_DEFAULT)+pipeliningLimit :: Int+pipeliningLimit = 100+#endif++pipeliningEnabledByDefault :: Bool+#ifdef CURL_PIPELINING_DEFAULT+pipeliningEnabledByDefault = True+#else+pipeliningEnabledByDefault = False+#endif++{-# NOINLINE maxPipeLength #-}+maxPipeLength :: IORef Int+maxPipeLength = unsafePerformIO $ newIORef $+#ifdef CURL_PIPELINING_DEFAULT+ pipeliningLimit+#else+ 1+#endif++{-# NOINLINE urlNotifications #-}+urlNotifications :: MVar (Map String (MVar String))+urlNotifications = unsafePerformIO $ newMVar Map.empty++{-# NOINLINE urlChan #-}+urlChan :: Chan UrlRequest+urlChan = unsafePerformIO $ do+ ch <- newChan+ forkIO (urlThread ch)+ return ch++urlThread :: Chan UrlRequest -> IO ()+urlThread ch = do junk <- flip showHex "" `fmap` randomRIO rrange+ evalStateT urlThread' (UrlState Map.empty emptyQ 0 junk)+ where rrange = (0, 2^(128 :: Integer) :: Integer)+ urlThread' = do+ empty <- liftIO $ isEmptyChan ch+ st <- get+ let l = pipeLength st+ w = waitToStart st+ reqs <- if not empty || (nullQ w && l == 0)+ then liftIO readAllRequests+ else return []+ mapM_ addReq reqs+ checkWaitToStart+ waitNextUrl+ urlThread'+ readAllRequests = do+ r <- readChan ch+ debugMessage $ "URL.urlThread ("++url r++"\n"+++ " -> "++file r++")"+ empty <- isEmptyChan ch+ reqs <- if not empty+ then readAllRequests+ else return []+ return (r:reqs)+ addReq r = do+ let u = url r+ f = file r+ c = cachable r+ d <- liftIO $ alreadyDownloaded u+ if d+ then dbg "Ignoring UrlRequest of URL that is already downloaded."+ else do+ st <- get+ let p = inProgress st+ w = waitToStart st+ e = (f, [], c, notifyVar r)+ new_w = case priority r of+ High -> pushQ u w+ Low -> insertQ u w+ new_st = st { inProgress = Map.insert u e p+ , waitToStart = new_w }+ case Map.lookup u p of+ Just (f', fs', c', v) -> do+ let new_c = minCachable c c'+ when (c /= c') $ let new_p = Map.insert u (f', fs', new_c, v) p+ in do modify (\s -> s { inProgress = new_p })+ dbg $ "Changing "++u++" request cachability from "++show c++" to "++show new_c+ when (u `elemQ` w && priority r == High) $ do+ modify (\s -> s { waitToStart = pushQ u (deleteQ u w) })+ dbg $ "Moving "++u++" to head of download queue."+ if f `notElem` (f':fs')+ then let new_p = Map.insert u (f', f:fs', new_c, v) p+ in do modify (\s -> s { inProgress = new_p })+ dbg "Adding new file to existing UrlRequest."+ else dbg "Ignoring UrlRequest of file that's already queued."+ _ -> put new_st+ alreadyDownloaded u = do+ n <- liftIO $ withMVar urlNotifications (return . (Map.lookup u))+ case n of+ Just v -> not `fmap` isEmptyMVar v+ Nothing -> return True++checkWaitToStart :: StateT UrlState IO ()+checkWaitToStart = do+ st <- get+ let l = pipeLength st+ mpl <- liftIO $ readIORef maxPipeLength+ when (l < mpl) $ do+ let w = waitToStart st+ case readQ w of+ Just (u,rest) -> do+ case Map.lookup u (inProgress st) of+ Just (f, _, c, v) -> do+ dbg ("URL.request_url ("++u++"\n"+++ " -> "++f++")")+ let f_new = f++"-new_"++randomJunk st+ err <- liftIO $ request_url u f_new c+ if null err+ then do dbg "URL.request_url succeeded"+ liftIO $ atexit (removeFileMayNotExist f_new)+ put $ st { waitToStart = rest+ , pipeLength = l + 1 }+ else do dbg $ "Failed to start download URL "++u++": "++err+ liftIO $ do removeFileMayNotExist f_new+ putMVar v err+ put $ st { waitToStart = rest }+ _ -> bug $ "Possible bug in URL.checkWaitToStart "++u+ checkWaitToStart+ _ -> return ()++copyUrlFirst :: String -> FilePath -> Cachable -> IO ()+copyUrlFirst = copyUrlWithPriority High++copyUrl :: String -> FilePath -> Cachable -> IO ()+copyUrl = copyUrlWithPriority Low++copyUrlWithPriority :: Priority -> String -> String -> Cachable -> IO ()+copyUrlWithPriority p u f c = do+ debugMessage ("URL.copyUrlWithPriority ("++u++"\n"+++ " -> "++f++")")+ v <- newEmptyMVar+ let fn _ old_val = old_val+ modifyMVar_ urlNotifications (return . (Map.insertWith fn u v))+ let r = UrlRequest u f c p v+ writeChan urlChan r++waitNextUrl :: StateT UrlState IO ()+waitNextUrl = do+ st <- get+ let l = pipeLength st+ when (l > 0) $ do+ dbg "URL.waitNextUrl start"+ (u, e) <- liftIO $ wait_next_url+ let p = inProgress st+ new_st = st { inProgress = Map.delete u p+ , pipeLength = l - 1 }+ liftIO $ if null e+ then case Map.lookup u p of+ Just (f, fs, _, v) -> do+ renameFile (f++"-new_"++randomJunk st) f+ mapM_ (safeCopyFile st f) fs+ putMVar v e+ debugMessage $ "URL.waitNextUrl succeeded: "++u++" "++f+ Nothing -> bug $ "Possible bug in URL.waitNextUrl: "++u+ else case Map.lookup u p of+ Just (f, _, _, v) -> do+ removeFileMayNotExist (f++"-new_"++randomJunk st)+ putMVar v e+ debugMessage $ "URL.waitNextUrl failed: "+++ u++" "++f++" "++e+ Nothing -> bug $ "Another possible bug in URL.waitNextUrl: "++u++" "++e+ unless (null u) $ put new_st+ where safeCopyFile st f t = let new_t = t++"-new_"++randomJunk st+ in do copyFile f new_t+ renameFile new_t t++waitUrl :: String -> IO ()+waitUrl u = do debugMessage $ "URL.waitUrl "++u+ r <- withMVar urlNotifications (return . (Map.lookup u))+ case r of+ Just var -> do+ e <- readMVar var+ modifyMVar_ urlNotifications (return . (Map.delete u))+ unless (null e) (debugFail $ "Failed to download URL "++u++": "++e)+ Nothing -> return () -- file was already downloaded++dbg :: String -> StateT a IO ()+dbg = liftIO . debugMessage++minCachable :: Cachable -> Cachable -> Cachable+minCachable Uncachable _ = Uncachable+minCachable _ Uncachable = Uncachable+minCachable (MaxAge a) (MaxAge b) = MaxAge $ min a b+minCachable (MaxAge a) _ = MaxAge a+minCachable _ (MaxAge b) = MaxAge b+minCachable _ _ = Cachable++#ifdef HAVE_CURL+cachableToInt :: Cachable -> CInt+cachableToInt Cachable = -1+cachableToInt Uncachable = 0+cachableToInt (MaxAge n) = n+#endif++setHTTPPipelining :: Bool -> IO ()+setHTTPPipelining False = writeIORef maxPipeLength 1+setHTTPPipelining True = writeIORef maxPipeLength+#ifdef CURL_PIPELINING+ pipeliningLimit+#else+ 1 >> (putStrLn $ "Warning: darcs is compiled without HTTP pipelining "+++ "support, '--http-pipelining' argument is ignored.")+#endif++setDebugHTTP :: IO ()+request_url :: String -> FilePath -> Cachable -> IO String+wait_next_url :: IO (String, String)++#ifdef HAVE_CURL++setDebugHTTP = curl_enable_debug++request_url u f cache =+ withCString u $ \ustr ->+ withCString f $ \fstr -> do+ err <- curl_request_url ustr fstr (cachableToInt cache) >>= peekCString+ return err++wait_next_url = do+ e <- curl_wait_next_url >>= peekCString+ u <- curl_last_url >>= peekCString+ return (u, e)++foreign import ccall "hscurl.h curl_request_url"+ curl_request_url :: CString -> CString -> CInt -> IO CString++foreign import ccall "hscurl.h curl_wait_next_url"+ curl_wait_next_url :: IO CString++foreign import ccall "hscurl.h curl_last_url"+ curl_last_url :: IO CString++foreign import ccall "hscurl.h curl_enable_debug"+ curl_enable_debug :: IO ()++#elif defined(HAVE_HTTP)++setDebugHTTP = return ()+request_url = HTTP.request_url+wait_next_url = HTTP.wait_next_url++#else++setDebugHTTP = debugMessage "URL.setDebugHttp works only with libcurl"+request_url _ _ _ = debugFail "URL.request_url: there is no libcurl!"+wait_next_url = debugFail "URL.wait_next_url: there is no libcurl!"++#endif++-- Usage of these environment variables happens in C code, so the+-- closest to "literate" user documentation is here, where the+-- offending function 'curl_request_url' is imported.+environmentHelpProxy :: ([String], [String])+environmentHelpProxy = (["HTTP_PROXY", "HTTPS_PROXY", "FTP_PROXY",+ "ALL_PROXY", "NO_PROXY"], [+ "If Darcs was built with libcurl, the environment variables HTTP_PROXY,",+ "HTTPS_PROXY and FTP_PROXY can be set to the URL of a proxy in the form",+ "",+ " [protocol://]<host>[:port]",+ "",+ "In which case libcurl will use the proxy for the associated protocol",+ "(HTTP, HTTPS and FTP). The environment variable ALL_PROXY can be used",+ "to set a single proxy for all libcurl requests.",+ "",+ "If the environment variable NO_PROXY is a comma-separated list of host",+ "names, access to those hosts will bypass proxies defined by the above",+ "variables. For example, it is quite common to avoid proxying requests",+ "to machines on the local network with",+ "",+ " NO_PROXY=localhost,*.localdomain",+ "",+ "For compatibility with lynx et al, lowercase equivalents of these",+ "environment variables (e.g. $http_proxy) are also understood and are",+ "used in preference to the uppercase versions.",+ "",+ "If Darcs was not built with libcurl, all these environment variables",+ "are silently ignored, and there is no way to use a web proxy."])++environmentHelpProxyPassword :: ([String], [String])+environmentHelpProxyPassword = (["DARCS_PROXYUSERPWD"], [+ "If Darcs was built with libcurl, and you are using a web proxy that",+ "requires authentication, you can set the $DARCS_PROXYUSERPWD",+ "environment variable to the username and password expected by the",+ "proxy, separated by a colon. This environment variable is silently",+ "ignored if Darcs was not built with libcurl."])
@@ -0,0 +1,173 @@+Copyright (c) 2002, members of the Haskell Internationalisation Working+Group All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++* Redistributions of source code must retain the above copyright notice,+ this list of conditions and the following disclaimer.+* Redistributions in binary form must reproduce the above copyright notice,+ this list of conditions and the following disclaimer in the+ documentation and/or other materials provided with the distribution.+* Neither the name of the Haskell Internationalisation Working Group nor+ the names of its contributors may be used to endorse or promote products+ derived from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE+POSSIBILITY OF SUCH DAMAGE.++This module provides lazy stream encoding/decoding facilities for UTF-8,+the Unicode Transformation Format with 8-bit words.++2002-09-02 Sven Moritz Hallberg <pesco@gmx.de>+++> {-# OPTIONS_GHC -cpp #-}+> module UTF8+> ( encode ) where+++SUNSET: we should remove this module after the 2009-07 release+of darcs and just use Codec.Binary.UTF8.String instead++#ifdef HAVE_UTF8STRING+> import qualified Codec.Binary.UTF8.String (encode)+> import Data.Word (Word8)+#else+> import Data.Char (ord)+> import Data.Word (Word8, Word16, Word32)+> import Data.Bits (Bits, shiftR, (.&.), (.|.))+#endif++++///- UTF-8 in General -///++Adapted from the Unicode standard, version 3.2,+Table 3.1 "UTF-8 Bit Distribution" (excluded are UTF-16 encodings):++ Scalar 1st Byte 2nd Byte 3rd Byte 4th Byte+ 000000000xxxxxxx 0xxxxxxx+ 00000yyyyyxxxxxx 110yyyyy 10xxxxxx+ zzzzyyyyyyxxxxxx 1110zzzz 10yyyyyy 10xxxxxx+ 000uuuzzzzzzyyyyyyxxxxxx 11110uuu 10zzzzzz 10yyyyyy 10xxxxxx++Also from the Unicode standard, version 3.2,+Table 3.1B "Legal UTF-8 Byte Sequences":++ Code Points 1st Byte 2nd Byte 3rd Byte 4th Byte+ U+0000..U+007F 00..7F+ U+0080..U+07FF C2..DF 80..BF+ U+0800..U+0FFF E0 A0..BF 80..BF+ U+1000..U+CFFF E1..EC 80..BF 80..BF+ U+D000..U+D7FF ED 80..9F 80..BF+ U+D800..U+DFFF ill-formed+ U+E000..U+FFFF EE..EF 80..BF 80..BF+ U+10000..U+3FFFF F0 90..BF 80..BF 80..BF+ U+40000..U+FFFFF F1..F3 80..BF 80..BF 80..BF+ U+100000..U+10FFFF F4 80..8F 80..BF 80..BF++++///- Encoding Functions -///++Must the encoder ensure that no illegal byte sequences are output or+can we trust the Haskell system to supply only legal values?+For now I include error case for the surrogate values U+D800..U+DFFF and+out-of-range scalars.++The function is pretty much a transscript of table 3.1B with error checks.+It dispatches the actual encoding to functions specific to the number of+required bytes.++#ifndef HAVE_UTF8STRING+> encodeOne :: Char -> [Word8]+> encodeOne c+>-- The report guarantees in (6.1.2) that this won't happen:+>-- | n < 0 = error "encodeUTF8: ord returned a negative value"+> | n < 0x0080 = encodeOne_onebyte n8+> | n < 0x0800 = encodeOne_twobyte n16+> | n < 0xD800 = encodeOne_threebyte n16+> | n < 0xE000 = error "encodeUTF8: ord returned a surrogate value"+> | n < 0x10000 = encodeOne_threebyte n16+>-- Haskell 98 only talks about 16 bit characters, but ghc handles 20.1.+> | n < 0x10FFFF = encodeOne_fourbyte n32+> | otherwise = error "encodeUTF8: ord returned a value above 0x10FFFF"+> where+> n = ord c :: Int+> n8 = fromIntegral n :: Word8+> n16 = fromIntegral n :: Word16+> n32 = fromIntegral n :: Word32+#endif+++With the above, a stream decoder is trivial:+++> encode :: [Char] -> [Word8]+#ifdef HAVE_UTF8STRING+> encode = Codec.Binary.UTF8.String.encode+#else+> encode = concatMap encodeOne+#endif+++Now follow the individual encoders for certain numbers of bytes...+ _+ / | __ ___ __ __+ / ^| // /__/ // //+ /.==| \\ //_ // //+It's // || // \_/_//_//_ and it's here to stay!++#ifndef HAVE_UTF8STRING+> encodeOne_onebyte :: Word8 -> [Word8]+> encodeOne_onebyte cp = [cp]+#endif+++00000yyyyyxxxxxx -> 110yyyyy 10xxxxxx++#ifndef HAVE_UTF8STRING+> encodeOne_twobyte :: Word16 -> [Word8]+> encodeOne_twobyte cp = [(0xC0.|.ys), (0x80.|.xs)]+> where+> xs, ys :: Word8+> ys = fromIntegral (shiftR cp 6)+> xs = (fromIntegral cp) .&. 0x3F+#endif+++zzzzyyyyyyxxxxxx -> 1110zzzz 10yyyyyy 10xxxxxx++#ifndef HAVE_UTF8STRING+> encodeOne_threebyte :: Word16 -> [Word8]+> encodeOne_threebyte cp = [(0xE0.|.zs), (0x80.|.ys), (0x80.|.xs)]+> where+> xs, ys, zs :: Word8+> xs = (fromIntegral cp) .&. 0x3F+> ys = (fromIntegral (shiftR cp 6)) .&. 0x3F+> zs = fromIntegral (shiftR cp 12)+#endif+++000uuuzzzzzzyyyyyyxxxxxx -> 11110uuu 10zzzzzz 10yyyyyy 10xxxxxx++#ifndef HAVE_UTF8STRING+> encodeOne_fourbyte :: Word32 -> [Word8]+> encodeOne_fourbyte cp = [0xF0.|.us, 0x80.|.zs, 0x80.|.ys, 0x80.|.xs]+> where+> xs, ys, zs, us :: Word8+> xs = (fromIntegral cp) .&. 0x3F+> ys = (fromIntegral (shiftR cp 6)) .&. 0x3F+> zs = (fromIntegral (shiftR cp 12)) .&. 0x3F+> us = fromIntegral (shiftR cp 18)+#endif
@@ -0,0 +1,94 @@+{-# OPTIONS_GHC -cpp #-}+{-# LANGUAGE CPP #-}++module Workaround ( renameFile, setExecutable, getCurrentDirectory,+ installHandler, raiseSignal, Handler(..), Signal,+ sigINT, sigHUP, sigABRT, sigALRM, sigTERM, sigPIPE ) where++#ifdef WIN32+import qualified System.Directory ( renameFile, getCurrentDirectory, removeFile )+import qualified Control.Exception ( block )+import qualified System.IO.Error ( isDoesNotExistError, ioError, catch )+#else+import System.Posix.Signals(installHandler, raiseSignal, Handler(..), Signal,+ sigINT, sigHUP, sigABRT, sigALRM, sigTERM, sigPIPE)+import System.Directory ( renameFile, getCurrentDirectory )+import System.Posix.Files (fileMode,getFileStatus, setFileMode,+ setFileCreationMask,+ ownerReadMode, ownerWriteMode, ownerExecuteMode,+ groupReadMode, groupWriteMode, groupExecuteMode,+ otherReadMode, otherWriteMode, otherExecuteMode)+import Data.Bits ( (.&.), (.|.), complement )+#endif++#ifdef WIN32+-- Dummy implementation of POSIX signals+data Handler = Default | Ignore | Catch (IO ())+type Signal = Int++installHandler :: Signal -> Handler -> Maybe () -> IO ()+installHandler _ _ _ = return ()++raiseSignal :: Signal -> IO ()+raiseSignal _ = return ()++sigINT, {- sigKILL, -} sigHUP, {- sigQUIT, -} sigABRT, sigALRM, sigTERM, sigPIPE :: Signal+sigINT = 0+-- not used: sigKILL = 0+sigHUP = 0+-- not used: sigQUIT = 0+sigABRT = 0+sigTERM = 0+sigPIPE = 0+sigALRM = 0++{-+System.Directory.renameFile incorrectly fails when the new file already+exists. This code works around that bug at the cost of losing atomic+writes.+-}++renameFile :: FilePath -> FilePath -> IO ()+renameFile old new = Control.Exception.block $+ System.Directory.renameFile old new+ `System.IO.Error.catch` \_ ->+ do System.Directory.removeFile new+ `System.IO.Error.catch`+ (\e -> if System.IO.Error.isDoesNotExistError e+ then return ()+ else System.IO.Error.ioError e)+ System.Directory.renameFile old new++setExecutable :: FilePath -> Bool -> IO ()+setExecutable _ _ = return ()++{-+System.Directory.getCurrentDirectory returns a path with backslashes in it+under windows, and some of the code gets confused by that, so we override+getCurrentDirectory and translates '\\' to '/'+-}++getCurrentDirectory :: IO FilePath+getCurrentDirectory = do d <- System.Directory.getCurrentDirectory+ return $ map rb d+ where rb '\\' = '/'+ rb c = c++#else++setExecutable :: FilePath -> Bool -> IO ()+setExecutable f ex =+ do st <- getFileStatus f+ umask <- setFileCreationMask 0+ setFileCreationMask umask+ let rw = fileMode st .&.+ (ownerReadMode .|. ownerWriteMode .|.+ groupReadMode .|. groupWriteMode .|.+ otherReadMode .|. otherWriteMode)+ total = if ex then rw .|.+ ((ownerExecuteMode .|. groupExecuteMode .|. otherExecuteMode)+ .&. complement umask)+ else rw+ setFileMode f total++#endif
@@ -0,0 +1,181 @@+/*+ Copyright (C) 2005 Juliusz Chroboczek++ This program is free software; you can redistribute it and/or modify+ it under the terms of the GNU General Public License as published by+ the Free Software Foundation; either version 2, or (at your option)+ any later version.++ This program is distributed in the hope that it will be useful,+ but WITHOUT ANY WARRANTY; without even the implied warranty of+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+ GNU General Public License for more details.++ You should have received a copy of the GNU General Public License+ along with this program; see the file COPYING. If not, write to+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+ Boston, MA 02110-1301, USA.+*/++#include <sys/types.h>+#include <sys/stat.h>+#include <fcntl.h>+#include <string.h>+#include <stdio.h>+#include <stdlib.h>++#include <unistd.h>+#include <errno.h>+#include <sys/time.h>++#ifdef _WIN32+#include <windows.h>+#include <io.h>+#endif++int sloppy_atomic_create(const char *p)+{+ int fd;+ fd = open(p, O_WRONLY | O_EXCL | O_CREAT, 0666);+ if(fd < 0)+ return -1;+ close(fd);+ return 1;+}++#ifdef _WIN32++int atomic_create(const char *p)+{+ return sloppy_atomic_create(p);+}++#else++static int careful_atomic_create(const char *p)+{+ /* O_EXCL is not available over NFSv2, and even under NFSv3, it is+ broken on many systems. The following protocol is provably+ safe assuming that:+ - creation of hard links is atomic;+ - stat hits the server rather than working from the cache.+ */++ static char hostname[65] = {'\0'};+ int fd, rc, saved_errno;+#define FILENAME_SIZE (11 + 15 + 8 + 1)+ char *filename;+ char *lastslash;+ int dirlen;+ struct timeval now;+ struct stat sb;++ if(hostname[0] == '\0') {+ char *c;+ int i;+ /* POSIX guarantees 65 is enough. */+ rc = gethostname(hostname, 65);+ if(rc < 0 || rc >= 65) {+ fprintf(stderr, "Error reading hostname when locking.\n");+ strcpy(hostname, "kremvax");+ }+ c = strchr(hostname, '.');+ if(c != NULL)+ *c = '\0';+ hostname[15] = '\0';+ /* clean up a few possible nasty characters folks might put in their hostname */+ for (i=0;i<15;i++)+ if (hostname[i] == ':' || hostname[i] == '/' || hostname[i] == '\\')+ hostname[i] = '-';+ }++ lastslash = strrchr(p, '/');+ dirlen = lastslash ? lastslash - p + 1 : 0;++ filename = malloc(dirlen + FILENAME_SIZE);+ if(filename == NULL)+ return -1;++ if(dirlen > 0)+ memcpy(filename, p, dirlen);+ filename[dirlen] = '\0';++ gettimeofday(&now, NULL);++ rc = snprintf(filename + dirlen, FILENAME_SIZE, "darcs_lock_%s%04x%04x",+ hostname, ((unsigned)getpid()) & 0xFFFF,+ ((unsigned)(now.tv_usec ^ (now.tv_usec >> 16))) & 0xFFFF);+ if(rc < 0 || rc >= FILENAME_SIZE) {+ fprintf(stderr, "Error writing to lock filename (%d)\n", + rc < 0 ? errno : 0);+ goto fail2;+ }++ fd = open(filename, O_WRONLY | O_EXCL | O_CREAT, 0666);+ if(fd < 0)+ goto fail2;++ /* Paranoia: should cause the client to flush its metadata cache. */+ rc = close(fd);+ if(rc < 0) {+ fprintf(stderr, "Error closing file %s. (%d)\n", filename, errno);+ goto fail;+ }++ rc = link(filename, p);+ if(rc >= 0)+ goto success;+ else if(errno == EPERM || errno == EOPNOTSUPP) {+ /* Linux returns EPERM when making hard links on filesystems+ that don't support them. */+ /* It seems that MacOS returns EOPNOTSUPP on filesystems that+ don't support hard links. */+ unlink(filename);+ free(filename);+ return sloppy_atomic_create(p);+ } else if(errno != EEXIST && errno != EIO)+ goto fail;++ /* The link may still have been successful if we're running over+ UDP and got EEXIST or EIO. Check the file's link count. */++ rc = stat(filename, &sb);+ if(rc < 0) {+ goto fail;+ }++ if(sb.st_nlink != 2) {+ errno = EEXIST;+ goto fail;+ }++ success:+ unlink(filename);+ free(filename);+ return 1;++ fail:+ saved_errno = errno;+ unlink(filename);+ errno = saved_errno;+ fail2:+ free(filename);+ return -1;+}++int atomic_create(const char *p)+{+ static int sloppy = -1;++ if(sloppy < 0) {+ char *s = getenv("DARCS_SLOPPY_LOCKS");+ sloppy = (s != NULL);+ }++ if(sloppy)+ return sloppy_atomic_create(p);+ else+ return careful_atomic_create(p);+}++#endif
@@ -0,0 +1,25 @@++#include <sys/stat.h>+#include <string.h>+#include <stdlib.h>++int sloppy_atomic_create(const char *p);+int atomic_create(const char *p);++#ifdef _WIN32+int mkstemp(char *p);+int pipe( int fildes[2] );+int renamefile(const char *from, const char *to);+#endif++int open_read(const char *fname);+int open_write(const char *fname);+int smart_wait(int pid);++int execvp_no_vtalarm(const char *file, char *const argv[]);++int is_symlink(const char *file);++int stdout_is_a_pipe();++int maybe_relink(const char *src, const char *dst, int careful);
@@ -0,0 +1,611 @@+% This file (unlike the rest of darcs) is in the public domain.+ ++\chapter{Best practices}++This chapter is intended to review various scenarios and describe in each+case effective ways of using darcs. There is no one ``best practice'', and+darcs is a sufficiently low-level tool that there are many high-level ways+one can use it, which can be confusing to new users. The plan (and hope)+is that various users will contribute here describing how they use darcs in+different environments. However, this is not a wiki, and contributions+will be edited and reviewed for consistency and wisdom.+++\section{Creating patches}++This section will lay down the concepts around patch creation.+The aim is to develop a way of thinking+that corresponds well to how darcs is behaving+--- even in complicated situations.++ In a single darcs repository you can think of two ``versions'' of the source tree.+ They are called the \emph{working} and \emph{pristine} trees.+ \emph{Working} is your normal source tree, with or without darcs alongside.+ The only thing that makes it part of a darcs repository+ is the \verb!_darcs! directory in its root.+ \emph{Pristine} is the recorded state of the source tree.+ The pristine tree is constructed from groups of changes,+ called {\em patches\/} (some other version control systems use the+ term {\em changeset\/} instead of {\em patch\/}).\footnote{If+ you look inside \_darcs you will find files or directories named+ {\tt patches} and {\tt inventories}, which store all the patches+ ever recorded. If the repository holds a cached pristine tree, it+ is stored in a directory called {\tt pristine} or {\tt current\/};+ otherwise, the fact that there is no pristine tree is marked+ by the presence of a file called {\tt pristine.none} or {\tt+ current.none}.}+ Darcs will create and store these patches+ based on the changes you make in \emph{working}.+++\subsection{Changes}+ If \emph{working} and \emph{pristine} are the same,+ there are ``no changes'' in the repository.+ Changes can be introduced (or removed) by editing the files in \emph{working}.+ They can also be caused by darcs commands,+ which can modify \emph{both} \emph{working} and \emph{pristine}.+ It is important to understand for each darcs command+ how it modifies \emph{working}, \emph{pristine} or both of them.++ \verb!whatsnew! (as well as \verb!diff!) can show+ the difference between \emph{working} and \emph{pristine} to you.+ It will be shown as a difference in \emph{working}.+ In advanced cases it need \emph{not} be \emph{working} that has changed;+ it can just as well have been \emph{pristine}, or both.+ The important thing is the difference and what darcs can do with it.++\subsection{Keeping or discarding changes}+ If you have a difference in \emph{working}, you do two things+ with it: \verb!record! it to keep it, or \verb!revert! it to lose the changes.%+ \footnote{%+ Revert can undo precious work in a blink.+ To protect you from great grief,+ the discarded changes are saved temporarily+ so the latest revert can be undone with unrevert.}++ If you have a difference between \emph{working} and \emph{pristine}%+ ---for example after editing some files in \emph{working}---%+ \verb!whatsnew! will show some ``unrecorded changes''.+ To save these changes, use \verb!record!.+ It will create a new patch in \emph{pristine} with the same changes,+ so \emph{working} and \emph{pristine} are no longer different.+ To instead undo the changes in \emph{working}, use \verb!revert!.+ It will modify the files in \emph{working} to be the same as in \emph{pristine}+ (where the changes do not exist).+++\subsection{Unrecording changes}+ \verb!unrecord! is a command meant to be run only in private+ repositories. Its intended purpose is to allow developers the flexibility+ to undo patches that haven't been distributed yet.++ However, darcs does not prevent you from unrecording a patch that+ has been copied to another repository. Be aware of this danger!++ If you \verb!unrecord! a patch, that patch will be deleted from \emph{pristine}.+ This will cause \emph{working} to be different from \emph{pristine},+ and \verb!whatsnew! to report unrecorded changes.+ The difference will be the same as just before that patch was \verb!record!ed.+ Think about it.+ \verb!record! examines what's different with \emph{working}+ and constructs a patch with the same changes in \emph{pristine}+ so they are no longer different.+ \verb!unrecord! deletes this patch;+ the changes in \emph{pristine} disappear and the difference is back.++ If the recorded changes included an error,+ the resulting flawed patch can be unrecorded.+ When the changes have been fixed,+ they can be recorded again as a new---hopefully flawless---patch.++ If the whole change was wrong it can be discarded from \emph{working} too,+ with \verb!revert!.+ \verb!revert! will update \emph{working} to the state of \emph{pristine},+ in which the changes do no longer exist after the patch was deleted.++ Keep in mind that the patches are your history,+ so deleting them with \verb!unrecord! makes it impossible to track+ what changes you \emph{really} made.+ Redoing the patches is how you ``cover the tracks''.+ On the other hand,+ it can be a very convenient way to manage and organize changes+ while you try them out in your private repository.+ When all is ready for shipping,+ the changes can be reorganized in what seems as useful and impressive patches.+ Use it with care.++ All patches are global,+ so don't \emph{ever} replace an already ``shipped'' patch in this way!+ If an erroneous patch is deleted and replaced with a better one,+ you have to replace it in \emph{all} repositories that have a copy of it.+ This may not be feasible, unless it's all private repositories.+ If other developers have already made patches or tags in their repositories+ that depend on the old patch, things will get complicated.+++\subsection{Special patches and pending}++The patches described in the previous sections have mostly been hunks.+A \emph{hunk} is one of darcs' primitive patch types,+and it is used to remove old lines and/or insert new lines.+There are other types of primitive patches,+such as \emph{adddir} and \emph{addfile}+which add new directories and files,+and \emph{replace}+which does a search-and-replace on tokens in files.++Hunks are always calculated in place with a diff algorithm+just before \verb!whatsnew! or \verb!record!.+But other types of primitive patches need to be explicitly created+with a darcs command.+They are kept in \emph{pending}%+\footnote{In the file {\tt\_darcs/patches/pending}.}+until they are either recorded or reverted.++\emph{Pending} can be thought of as a special extension of \emph{working}.+When you issue, e.g., a darcs \verb!replace! command,+the replace is performed on the files in \emph{working}+and at the same time a replace patch is put in \emph{pending}.+Patches in \emph{pending} describe special changes made in \emph{working}.+The diff algorithm will fictively apply these changes to \emph{pristine}+before it compares it to \emph{working},+so all lines in \emph{working} that are changed by a \verb!replace! command+will also be changed in \emph{pending}$+$\emph{pristine}+when the hunks are calculated.+That's why no hunks with the replaced lines will be shown by \verb!whatsnew!;+it only shows the replace patch in \emph{pending} responsible for the change.++If a special patch is recorded, it will simply be moved to \emph{pristine}.+If it is instead reverted, it will be deleted from \emph{pending}+and the accompanying change will be removed from \emph{working}.++Note that reverting a patch in pending is \emph{not} the same as+simply removing it from pending.+It actually applies the inverse of the change to \emph{working}.+Most notable is that reverting an addfile patch+will delete the file in \emph{working} (the inverse of adding it).+So if you add the wrong file to darcs by mistake,+\emph{don't} \verb!revert! the addfile.+Instead use \verb!remove!, which cancels out the addfile in pending.+++\section{Using patches} % still basics++This section will lay down the concepts around patch distribution and branches.+The aim is to develop a way of thinking+that corresponds well to how darcs is behaving+--- even in complicated situations.++A repository is a collection of patches.+Patches have no defined order,+but patches can have dependencies on other patches.+Patches can be added to a repository in any order+as long as all patches depended upon are there.+Patches can be removed from a repository in any order,+as long as no remaining patches depend on them.++Repositories can be cloned to create branches.+Patches created in different branches may conflict.+A conflict is a valid state of a repository.+A conflict makes the working tree ambiguous until the conflict is resolved.+++\subsection{Dependencies}++There are two kinds of dependencies:+implicit dependencies and explicit dependencies.++Implicit dependencies is the far most common kind.+These are calculated automatically by darcs.+If a patch removes a file or a line of code,+it will have to depend on the patch that added that file or line of code.\footnote{%+Actually it doesn't have to---in theory---,+but in practice it's hard to create ``negative'' files or lines in the working tree.+See the chapter about Theory of patches for other constraints.}+If a patch adds a line of code,+it will usually have to depend on the patch or patches that added the adjacent lines.++Explicit dependencies can be created if you give the \verb|--ask-deps| option to \verb|darcs record|.+This is good for assuring that logical dependencies hold between patches.+It can also be used to group patches---%+a patch with explicit dependencies doesn't need to change anything---%+and pulling the patch also pulls all patches it was made to depend on.+++\subsection{Branches: just normal repositories}++Darcs does not have branches---it doesn't need to.+Every repository can be used as a branch.+This means that any two repositories are ``branches'' in darcs,+but it is not of much use unless they have a large portion of patches in common.+If they are different projects they will have nothing in common,+but darcs may still very well be able to merge them,+although the result probably is nonsense.+Therefore the word ``branch'' isn't a technical term in darcs;+it's just the way we think of one repository in relation to another.++Branches are \emph{very} useful in darcs.+They are in fact \emph{necessary} if you want to do more than only simple work.+When you \verb|get| someone's repository from the Internet,+you are actually creating a branch of it.+It may first seem inefficient (or if you come from CVS---frightening),+not to say plain awkward.+But darcs is designed this way, and it has means to make it efficient.+The answer to many questions about how to do a thing with darcs is: ``use a branch''.+It is a simple and elegant solution with great power and flexibility,+which contributes to darcs' uncomplicated user interface.++You create new branches (i.e., clone repositories)+with the \verb|get| and \verb|put| commands.+++\subsection{Moving patches around---no versions}++Patches are global, and a copy of a patch either is or is not present in a branch.+This way you can rig a branch almost any way you like,+as long as dependencies are fulfilled---%+darcs \emph{won't} let you break dependencies.+If you suspect a certain feature from some time ago introduced a bug,+you can remove the patch/patches that adds the feature,+and try without it.\footnote{%+darcs even has a special command, {\tt trackdown}+that automatically removes patches+until a specified test no longer fails.}++Patches are added to a repository with \verb|pull|+and removed from the repositories with \verb|obliterate|.+Don't confuse these two commands with \verb|record| and \verb|unrecord|,+which constructs and deconstructs patches.++It is important not to lose patches when (re)moving them around.+\verb|pull| needs a source repository to copy the patch from,+whereas \verb|obliterate| just erases the patch.+Beware that if you obliterate \emph{all} copies of a patch+it is completely lost---forever.+Therefore you should work with branches when you obliterate patches.+The \verb|obliterate| command can wisely be disabled in a dedicated main repository+by adding \verb|obliterate disable| to the repository's defaults file.++For convenience, there is a \verb|push| command.+It works like \verb|pull| but in the other direction.+It also differs from \verb|pull| in an important way:+it starts a second instance of darcs to apply the patch in the target repository,+even if it's on the same computer.+It can cause surprises if you have a ``wrong'' darcs in your PATH.+++\subsection{Tags---versions}++While \verb|pull| and \verb|obliterate| can be used to+construct different ``versions'' in a repository,+it is often desirable to name specific configurations of patches+so they can be identified and retrieved easily later.+This is how darcs implements what is usually known as versions.+The command for this is \verb|tag|,+and it records a tag in the current repository.++A tag is just a patch, but it only contains explicit dependencies.+It will depend on all the patches in the current repository.\footnote{%+It will omit patches already depended upon by other patches,+since they will be indirectly depended upon anyway.}+Darcs can recognize if a patch is as a tag;+tags are sometimes treated specially by darcs commands.++While traditional revision control systems tag versions in the time line history,+darcs lets you tag any configuration of patches at any time,+and pass the tags around between branches.++With the option \verb|--tag| to \verb|get| you can easily get+a named version in the repository+as a new branch.+++\subsection{Conflicts}++This part of darcs becomes a bit complicated,+and the description given here is slightly simplified.++Conflicting patches are created when+you record changes to the same line in two different repositories.+Same line does \emph{not} mean the same line number and file name,+but the same line added by a common depended-upon patch.++If you are using a darcs-2 repository (Section \ref{initialize}),+darcs does \emph{not} consider two patches making the \emph{same} change to be a+conflict, much in the same fashion as other version control systems.+(The caveat here is two non-identical patches with some identical+changes may conflict. For the most part, darcs should just do what you+expect).++A conflict \emph{happens} when two conflicting patches meet in the same repository.+This is no problem for darcs; it can happily pull together just any patches.+But it is a problem for the files in \emph{working} (and \emph{pristine}).+The conflict can be thought of as+two patches telling darcs different things about what a file should look like.++Darcs escapes this problem+by ignoring those parts\footnote{%+The primitive patches making up the total patch.}+of the patches that conflict.+They are ignored in \emph{both} patches.+If patch~A changes the line ``FIXME'' to ``FIXED'',+and patch~B changes the same line to ``DONE'',+the two patches together will produce the line ``FIXME''.+Darcs doesn't care which one you pulled into the repository first,+you still get the same result when the conflicting patches meet.+All other changes made by A and B are performed as normal.++Darcs can mark a conflict for you in \emph{working}.+This is done with \verb|mark-conflicts|.+Conflicts are marked such that both conflicting changes+are inserted with special delimiter lines around them.+Then you can merge the two changes by hand,+and remove the delimiters.++When you pull patches,+darcs automatically performs a \verb|mark-conflicts| for you if a conflict happens.+You can remove the markup with \verb|revert|,+Remember that the result will be the lines from+the previous version common to both conflicting patches.+The conflict marking can be redone again with \verb|mark-conflicts|.++A special case is when a pulled patch conflicts with unrecorded changes in the repository.+The conflict will be automatically marked as usual,+but since the markup is \emph{also} an unrecorded change,+it will get mixed in with your unrecorded changes.+There is no guarantee you can revert \emph{only} the markup after this,+and \verb|resolve| will not be able to redo this markup later if you remove it.+It is good practice to record important changes before pulling.++\verb|mark-conflicts| can't mark complicated conflicts.+In that case you'll have to use \verb|darcs diff| and other commands+to understand what the conflict is all about.+If for example two conflicting patches create the same file,+\verb|mark-conflicts| will pick just one of them,+and no delimiters are inserted.+So watch out if darcs tells you about a conflict.++\verb|mark-conflicts| can also be used to check for unresolved conflicts.+If there are none, darcs replies ``No conflicts to resolve''.+While \verb|pull| reports when a conflict happens,+\verb|obliterate| and \verb|get| don't.+++\subsection{Resolving conflicts}++A conflict is resolved+(not marked, as with the command \verb|mark-conflicts|)+as soon as some new patch depends on the conflicting patches.+This will usually be the resolve patch you record after manually putting together the pieces+from the conflict markup produced by \verb|mark-conflicts| (or \verb|pull|).+But it can just as well be a tag.+So don't forget to fix conflicts before you accidently ``resolve'' them by recording other patches.++If the conflict is with one of your not-yet-published patches,+you may choose to amend that patch rather than creating a resolve patch.++If you want to back out and wait with the conflict,+you can \verb|obliterate| the conflicting patch you just pulled.+Before you can do that you have to \verb|revert| the conflict markups+that \verb|pull| inserted when the conflict happened.++\section{Use a Global Cache}++When working with darcs 2 it is recommended to use a global cache, as this+is one of the biggest performance enhancing tools of darcs 2. The global+cache acts as a giant patch pool where darcs first looks for a patch when+grabbing new patches. This saves time by not downloading the same patch+twice from a remote server. It also saves space by storing the patch only+once, if you ensure your cache and your repositories are on the same+hardlink-supporting filesystem. ++Darcs now enables a global patch cache under your home directory by default.+Older darcs 2.x versions required this manual step:++\begin{verbatim}+$ mkdir -p $HOME/.darcs/cache+$ echo cache:$HOME/.darcs/cache > $HOME/.darcs/sources+\end{verbatim}++On MS Windows~\ref{ms_win}, using \verb|cmd.exe| (Command Prompt under Accessories):++\begin{verbatim}+> md "%UserProfile%\Application Data\darcs\cache" (notice double quotes!)+> echo cache:%UserProfile%\Application Data\darcs\cache > "%UserProfile%\Application Data\darcs\sources"+\end{verbatim}++There are some other advanced things you can do in \verb!_darcs/prefs/sources!,+such as create per-repository caches, read-only caches and even set a+primary source repository above any used in a \verb|darcs get| or +\verb|darcs pull| command.++\section{Distributed development with one primary developer}+\label{darcs-development-practices}++This is how darcs itself is developed. There are many contributors to+darcs, but every contribution is reviewed and manually applied by myself.+For this sort of a situation, \verb|darcs send| is ideal, since the barrier for+contributions is very low, which helps encourage contributors.++One could simply set the \verb!_darcs/prefs/email! value to the project+mailing list, but I also use darcs send to send my changes to the main+server, so instead the email address is set to+``\verb!Davids Darcs Repo <droundy@abridgegame.org>!''. My+\verb-.procmailrc-+file on the server has the following rule:+\begin{verbatim}+:0+* ^TODavids Darcs Repo+|(umask 022; darcs apply --reply darcs-devel@abridgegame.org \+ --repodir /path/to/repo --verify /path/to/allowed_keys)+\end{verbatim}+This causes darcs apply to be run on any email sent to ``Davids Darcs+Repo''.+\verb'apply' actually applies them only if they are signed by an+authorized key. Currently, the only authorized key is mine, but of course+this could be extended easily enough.++The central darcs repository contains the following values in its+\verb!_darcs/prefs/defaults!:+\begin{verbatim}+apply test+apply verbose+apply happy-forwarding+\end{verbatim}+The first line tells apply to always run the test suite. The test suite is+in fact the main reason I use send rather than push, since it allows me to+easily continue working (or put my computer to sleep) while the tests are+being run on the main server. The second line is just there to improve the+email response that I get when a patch has either been applied or failed+the tests. The third line makes darcs not complain about unsigned patches,+but just to forward them to \verb!darcs-devel!.++On my development computer, I have in my \verb!.muttrc! the following+alias, which allows me to easily apply patches that I get via email+directly to my darcs working directory:+\begin{verbatim}+macro pager A "<pipe-entry>(umask 022; darcs apply --no-test -v \+ --repodir ~/darcs)"+\end{verbatim}+++\section{Development by a small group of developers in one office}+\label{dft-development-practices}++This section describes the development method used for the density+functional theory code DFT++, which is available at+\verb!http://dft.physics.cornell.edu/dft!.++We have a number of workstations which all mount the same \verb!/home! via NFS.+We created a special ``dft'' user, with the central repository living in that+user's home directory. The ssh public keys of authorized persons are added to+the ``dft'' user's \verb!.ssh/allowed_keys!, and we commit patches to this+repository using+\verb'darcs push'. As in Section~\ref{darcs-development-practices},+we have the central repository set to run the test suite before the push goes+through.++Note that no one ever runs as the dft user.++A subtlety that we ran into showed up in the running of the test suite.+Since our test suite includes the running of MPI programs, it must be run+in a directory that is mounted across our cluster. To achieve this, we set+the \verb!$DARCS_TMPDIR! % following is added to make emacs color right:$+environment variable to \verb!~/tmp!.++Note that even though there are only four active developers at the moment,+the distributed nature of darcs still plays a large role. Each developer+works on a feature until it is stable, a process that often takes quite a+few patches, and only once it is stable does he+\verb'push' to the central repository.++\section{Personal development}++It's easy to have several personal development trees using darcs, even+when working on a team or with shared code. The number and method of+using each personal tree is limited only by such grand limitations as:+your disk space, your imagination, available time, etc.++For example, if the central darcs repository for your development team+is $R_{c}$, you can create a local working directory for feature+$f_1$. This local working directory contains a full copy of $R_c$+(as of the time of the ``darcs get'' operation) and can be denoted+$R_1$. In the midst of working on feature $f_1$, you realize it+requires the development of a separate feature $f_2$. Rather than+intermingling $f_1$ and $f_2$ in the same working tree, you can create+a new working tree for working on $f_2$, where that working tree+contains the repository $R_2$.++While working on $f_2$, other developers may have made other changes;+these changes can be retrieved on a per-patch selection basis by+periodic ``darcs pull'' operations.++When your work on $f_2$ is completed, you can publish it for the use+of other developers by a ``darcs push'' (or ``darcs send'') from $R_2$+to $R_c$. Independently of the publishing of $f_2$, you can merge+your $f_2$ work into your $f_1$ working tree by a ``darcs pull $R_2$''+in the $R_1$ development tree (or ``darcs push'' from $R_2$ to $R_1$).++When your work on $f_1$ is completed, you publish it as well by a+``darcs push'' from $R_1$ to $R_c$. ++Your local feature development efforts for $f_1$ or $f_2$ can each+consist of multiple patches. When pushing or pulling to other trees,+these patches can either all be selected or specific patches can be+selected. Thus, if you introduce a set of debugging calls into the+code, you can commit the debugging code in a distictly separate patch+(or patches) that you will not push to $R_c$.++\subsection{Private patches}++As discussed in the section above, a developer may have various+changes to their local development repositories that they do not ever+wish to publish to a group repository (e.g. personal debugging code),+but which they would like to keep in their local repository, and+perhaps even share amongst their local repositories.++This is easily done via darcs, since those private changes can be+committed in patches that are separate from other patches; during the+process of pushing patches to the common repository ($R_c$), the+developer is queried for which patches should be moved to ($R_c$) on a+patch-by-patch basis.++The \verb!--complement! flag for the ``darcs pull'' operation can+further simplify this effort. If the developer copies (via ``darcs+push'' or ``darcs pull'') all private patches into a special+repository/working tree ($R_p$), then those patches are easily+disregarded for pulling by adding \verb!--complement! to the ``darcs+pull'' line and listing $R_p$ after the primary source repository.++The \verb!--complement! flag is only available for ``darcs pull'', and+not ``darcs push'' or ``darcs send'', requiring the user to have pull+access to the target repository. While the actual public repository+is often not shared in this manner, it's simple to create a local+version of the public repository to act as the staging area for that+public repository.++The following example extends the two feature addition example in the+previous section using a local staging repository ($R_l$) and a+private patch repository:++\begin{verbatim}+$ cd working-dir+$ darcs get http://server/repos/Rc Rl++$ darcs get Rl R1+$ cd R1+...development of f1+$ darcs record -m'p1: f1 initial work'+...+$ darcs record -m'p2: my debugging tracepoints'+...++$ cd ..+$ darcs get http://server/repos/Rc R2+$ cd R2+$ darcs pull -p p2 ../R1+... development of f2+$ darcs record -m'p3: f2 finished'++$ cd ..+$ darcs get Rl Rp+$ cd Rp+$ darcs pull -p p2 ../R2++$ cd ../Rl+$ darcs pull --complement ../R2 ../Rp+$ darcs send+... for publishing f2 patches to Rc++$ cd ../R1+$ darcs pull ../R2+... updates R1 with f2 changes from R2+... more development of f1+$ darcs record -m'p4: f1 feature finished.'++$ cd ../Rl+$ darcs pull --complement ../R1 ../Rp+$ darcs send+\end{verbatim}+
@@ -0,0 +1,93 @@+\chapter{Building darcs}+\section{The Easy Way}+If your distribution provides a pre-built binary package of a recent+Darcs release, you are strongly encouraged to use that rather than+building Darcs yourself.++If you have (or can install) the \texttt{cabal-install} package, this+is the next best option, as this will resolve build dependencies+automatically. To download, compile and install Darcs and its+dependencies via cabal-install, simply run+\begin{verbatim}+cabal update+cabal install darcs+\end{verbatim}++\section{The Hard Way}+If you cannot install \texttt{cabal-install}, you need to run+\begin{verbatim}+ghc --make Setup+./Setup configure+./Setup build+./Setup install+\end{verbatim}++This will require the following build dependencies:+\begin{itemize}+\item GHC 6.8 or higher; and+\item Cabal 1.6 or higher.+\end{itemize}++%% Note: darcs.cabal tends to have build dependencies [x,y), but I+%% couldn't work out a way to describe this concisely (and+%% intelligibly to non-mathematicians). So I have lied and simply+%% said `or higher', since the upper bounds usually only declare+%% incompatibility with versions that don't exist yet. --twb, 2009++Additional build dependencies are declared in the \texttt{darcs.cabal}+file, and \texttt{./Setup configure} will tell you if any required+build dependencies aren't found. The build dependencies at time of+writing (Darcs 2.3) are as follows:+\begin{itemize}+\item the C library zlib;+\item the Haskell packages+ \begin{itemize}+ \item array 1.1 or 1.2;+ \item containers 1.1 or 1.2;+ \item directory 1.0;+ \item filepath 1.1;+ \item hashed-storage 0.3;+ \item haskeline 0.6.1 or higher;+ \item html 1.0;+ \item mtl 1.0 or 1.1;+ \item old-time 1.0;+ \item parsec 2.1 or higher;+ \item process 1.0;+ \item random 1.0; and+ \item regex-compat 0.71 or higher.+ \end{itemize}+\end{itemize}++Missing \emph{optional} build dependencies are only listed if+\texttt{./Setup configure} is passed the \texttt{--verbose} argument.+At time of writing they are:+\begin{itemize}+\item the C library libcurl (7.19.1 or higher recommended);+\item the Haskell packages+ \begin{itemize}+ \item bytestring 0.9;+ \item bytestring-mmap 0.2 or higher;+ \item http 3000 or 3001.1;+ \item network 2.2;+ \item terminfo 0.3.+ \item utf8-string 0.3; and+ \item zlib 0.5.+ \end{itemize}+\end{itemize}++\section{The Old Way}+Darcs can still be built with GNU autotools; this approach is+deprecated and will be removed after the 2.3 release. Run+\begin{verbatim}+autoconf+./configure+make+make install+\end{verbatim}++The build dependencies are identical to the previous approach, except+that instead of the \texttt{Cabal} package, you will need+\begin{itemize}+\item GNU Make; and+\item GNU autoconf 2.50 or higher.+\end{itemize}
@@ -0,0 +1,88 @@++#include <sys/types.h>+#include <sys/stat.h>+#include <fcntl.h>+#include <string.h>+#include <stdio.h>+#include <stdlib.h>++#include <unistd.h>+#include <errno.h>+#include <sys/time.h>+#include <time.h>++#ifdef _WIN32+#include <windows.h>+#include <io.h>+#endif++#ifndef _WIN32++int open_read(const char *fname) {+ return open(fname, O_RDONLY);+}++int open_write(const char *fname) {+ return open(fname, O_WRONLY | O_TRUNC | O_CREAT,+ S_IWUSR | S_IRUSR | S_IROTH | S_IRGRP);+}++int get_errno() {+ return errno;+}++#include <sys/wait.h>+#include <signal.h>+#if HAVE_SIGINFO_H+#include <siginfo.h>+#endif++/* This waits and then returns the exit status of the process that was+ waited for. */+int smart_wait(int pid) {+ int stat;+ pid_t rc;++ do {+ rc = waitpid(pid, &stat, 0);+ } while(rc < 0 && errno == EINTR);++ if(rc < 0) {+ perror("waitpid");+ return -138;+ } else if (WIFEXITED(stat)) {+ return WEXITSTATUS(stat);+ } else if (WIFSIGNALED(stat)) {+ /* Fixme: psignal isn't portable (not in POSIX). */+ psignal(WTERMSIG(stat), "Error in subprocess");+ return - WTERMSIG(stat);+ } else {+ return -137;+ }+}++#include <unistd.h>+#include <sys/time.h>+#include <errno.h>+#include <stdio.h>++int execvp_no_vtalarm(const char *file, char *const argv[]) {+ /* Reset the itimers in the child, so it doesn't get plagued+ * by SIGVTALRM interrupts.+ */+ struct timeval tv_null = { 0, 0 };+ struct itimerval itv;+ itv.it_interval = tv_null;+ itv.it_value = tv_null;+ setitimer(ITIMER_REAL, &itv, NULL);+ setitimer(ITIMER_VIRTUAL, &itv, NULL);+ setitimer(ITIMER_PROF, &itv, NULL);+ execvp(file, argv);+ perror("Error in execvp");+ return errno;+}++#include <sys/types.h>+#include <sys/stat.h>+#endif+
@@ -0,0 +1,12 @@++#include <sys/stat.h>+#include <string.h>+#include <stdlib.h>++int open_read(const char *fname);+int open_write(const char *fname);+int smart_wait(int pid);+int get_errno();++int execvp_no_vtalarm(const char *file, char *const argv[]);+
@@ -0,0 +1,264 @@+% Copyright (C) 2004 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; if not, write to the Free Software Foundation,+% Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.++\chapter{Configuring darcs}\label{configuring}++There are several ways you can adjust darcs' behavior to suit your needs.+The first is to edit files in the \verb!_darcs/prefs/! directory of a+repository. Such configuration only applies when working with that+repository. To configure darcs on a per-user rather than per-repository+basis (but with essentially the same methods), you can edit (or create)+files in the \verb!~/.darcs/! directory.+Finally, the behavior of some darcs commands can be modified by setting+appropriate environment variables.++\paragraph{Microsoft Windows}\label{ms_win}++The global darcs directory is \verb!%APPDATA%\darcs\!. This typically expands to+\texttt{C:\textbackslash{}Documents And Settings\textbackslash{}\emph{user}\textbackslash{}Application Data\textbackslash{}darcs\textbackslash{}}.+This folder contains the cache, as well as all the per-user+settings files: preferences, boring etc... These will became the new defaults+that can be overridden on per-repository basis.++\input{Darcs/Repository/Prefs.lhs}++\input{Darcs/Repository/Motd.lhs}++\section{Environment variables}++There are a few environment variables whose contents affect darcs'+behavior. Here is a quick list of all the variables and their+documentation in the rest of the manual:++\begin{tabular}{|l|r|}+\hline+\textbf{Variable} & \textbf{Section} \\+\hline+DARCS\_EDITOR, EDITOR, VISUAL & \ref{env:DARCS_EDITOR} \\+DARCS\_PAGER, PAGER & \ref{env:DARCS_PAGER} \\+HOME & \ref{env:HOME} \\+TERM & \ref{env:TERM} \\+\hline+DARCS\_EMAIL, EMAIL & \ref{env:DARCS_EMAIL} \\+\hline+DARCS\_APPLY\_FOO & \ref{env:DARCS_X_FOO} \\+DARCS\_GET\_FOO & \ref{env:DARCS_X_FOO} \\+DARCS\_MGET\_FOO & \ref{env:DARCS_X_FOO} \\+DARCS\_MGETMAX & \ref{env:DARCS_MGETMAX} \\+DARCS\_PROXYUSERPWD & \ref{env:DARCS_PROXYUSERPWD} \\+DARCS\_SSH & \ref{env:DARCS_SSH} \\+DARCS\_SCP & \ref{env:DARCS_SCP} \\+DARCS\_SFTP & \ref{env:DARCS_SFTP} \\+SSH\_PORT & \ref{env:SSH_PORT} \\+\hline+DARCS\_ALTERNATIVE\_COLOR & \ref{env:DARCS_ALWAYS_COLOR}\\+DARCS\_ALWAYS\_COLOR & \ref{env:DARCS_ALWAYS_COLOR}\\+DARCS\_DO\_COLOR\_LINES & \ref{env:DARCS_DO_COLOR_LINES}\\+DARCS\_DONT\_COLOR & \ref{env:DARCS_ALWAYS_COLOR} \\+DARCS\_DONT\_ESCAPE\_TRAILING\_CR & \ref{env:DARCS_DONT_ESCAPE_white}\\+DARCS\_DONT\_ESCAPE\_TRAILING\_SPACES & \ref{env:DARCS_DONT_ESCAPE_white} \\+DARCS\_DONT\_ESCAPE\_8BIT & \ref{env:DARCS_DONT_ESCAPE_nonascii}\\+DARCS\_DONT\_ESCAPE\_ANYTHING & \ref{env:DARCS_DONT_ESCAPE_nonascii}\\+DARCS\_DONT\_ESCAPE\_ISPRINT & \ref{env:DARCS_DONT_ESCAPE_nonascii}\\+DARCS\_ESCAPE\_EXTRA & \ref{env:DARCS_DONT_ESCAPE_nonascii}\\+DARCS\_DONT\_ESCAPE\_EXTRA & \ref{env:DARCS_DONT_ESCAPE_nonascii}\\+\hline+\end{tabular}++\section{General-purpose variables}++\darcsEnv{DARCS_EDITOR}+\darcsEnv{DARCS_PAGER}+\darcsEnv{DARCS_TMPDIR}+\darcsEnv{DARCS_KEEP_TMPDIR}+\darcsEnv{HOME}++\section{Remote repositories}+\darcsEnv{DARCS_SSH}+\darcsEnv{DARCS_SCP}+\darcsEnv{SSH_PORT}+\darcsEnv{HTTP_PROXY}+\darcsEnv{DARCS_PROXYUSERPWD}++\paragraph{DARCS\_GET\_FOO, DARCS\_MGET\_FOO and DARCS\_APPLY\_FOO}+\label{env:DARCS_X_FOO}+When trying to access a repository with a URL beginning foo://,+darcs will invoke the program specified by the DARCS\_GET\_FOO+environment variable (if defined) to download each file, and the+command specified by the DARCS\_APPLY\_FOO environment variable (if+defined) when pushing to a foo:// URL. ++This method overrides all other ways of getting \verb!foo://xxx! URLs.++Note that each command should be constructed so that it sends the downloaded+content to STDOUT, and the next argument to it should be the URL\@. Here are some+examples that should work for DARCS\_GET\_HTTP:++\begin{verbatim}+fetch -q -o - +curl -s -f+lynx -source +wget -q -O -+\end{verbatim}++Apart from such toy examples, it is likely that you will need to+manipulate the argument before passing it to the actual fetcher+program. For example, consider the problem of getting read access to+a repository on a CIFS (SMB) share without mount privileges:++\begin{verbatim}+export DARCS_GET_SMB="smbclient -c get"+darcs get smb://fs/twb/Desktop/hello-world+\end{verbatim}++The above command will not work for several reasons. Firstly, Darcs+will pass it an argument beginning with `smb:', which smbclient does+not understand. Secondly, the host and share `//fs/twb' must be+presented as a separate argument to the path `Desktop/hello-world'.+Thirdly, smbclient requires that `get' and the path be a single+argument (including a space), rather than two separate arguments.+Finally, smbclient's `get' command writes the file to disk, while+Darcs expects it to be printed to standard output.++In principle, we could get around such problems by making the variable+contain a shell script, e.g.++\begin{verbatim}+export DARCS_GET_SMB='sh -c "...; smbclient $x -c \"get $y\""'+\end{verbatim}++Unfortunately, Darcs splits the command on whitespace and does not+understand that quotation or escaping, so there is no way to make+Darcs pass the text after `-c' to sh as a single argument. Therefore,+we instead need to put such one-liners in separate, executable scripts.++Continuing our smbclient example, we create an executable script+\verb|~/.darcs/libexec/get_smb| with the following contents:++\begin{verbatim}+#!/bin/bash -e+IFS=/ read host share file <<<"${1#smb://}"+smbclient //$host/$share -c "get $file -"+\end{verbatim}++And at last we can say++\begin{verbatim}+export DARCS_GET_SMB=~/.darcs/libexec/get_smb+darcs get smb://fs/twb/Desktop/hello-world+\end{verbatim}+++If set, DARCS\_MGET\_FOO+will be used to fetch many files from a single repository simultaneously.+Replace FOO and foo as appropriate to handle other URL schemes.+These commands are \emph{not} interpreted by a shell, so you cannot+use shell metacharacters, and the first word in the command must+be the name of an executable located in your path. The GET command+will be called with a URL for each file. The MGET command will be+invoked with a number of URLs and is expected to download the files+to the current directory, preserving the file name but not the path.+The APPLY command will be called with a darcs patchfile piped into+its standard input. Example:++\begin{verbatim}+wget -q +\end{verbatim}++\paragraph{DARCS\_MGETMAX}+\label{env:DARCS_MGETMAX}+When invoking a DARCS\_MGET\_FOO command, darcs will limit the+number of URLs presented to the command to the value of this variable,+if set, or 200.++These commands are \emph{not} interpreted by a shell, so you cannot use shell+meta-characters.++\section{Highlighted output}+\label{env:DARCS_ALWAYS_COLOR}+\label{env:DARCS_DO_COLOR_LINES}+\label{env:DARCS_DONT_ESCAPE_white}++If the terminal understands ANSI color escape sequences,+darcs will highlight certain keywords and delimiters when printing patches.+This can be turned off by setting the environment variable DARCS\_DONT\_COLOR to 1.+If you use a pager that happens to understand ANSI colors, like \verb!less -R!,+darcs can be forced always to highlight the output+by setting DARCS\_ALWAYS\_COLOR to 1.+If you can't see colors you can set DARCS\_ALTERNATIVE\_COLOR to 1,+and darcs will use ANSI codes for bold and reverse video instead of colors.+In addition, there is an extra-colorful mode, which is not enabled by+default, which can be activated with DARCS\_DO\_COLOR\_LINES.++By default darcs will escape (by highlighting if possible) any kind of spaces at the end of lines+when showing patch contents.+If you don't want this you can turn it off by setting+DARCS\_DONT\_ESCAPE\_TRAILING\_SPACES to 1.+A special case exists for only carriage returns:+DARCS\_DONT\_ESCAPE\_TRAILING\_CR.+++\section{Character escaping and non-ASCII character encodings}+\label{env:DARCS_DONT_ESCAPE_nonascii}++Darcs needs to escape certain characters when printing patch contents to a terminal.+Characters like \emph{backspace} can otherwise hide patch content from the user,+and other character sequences can even in some cases redirect commands to the shell+if the terminal allows it.++By default darcs will only allow printable 7-bit ASCII characters (including space),+and the two control characters \emph{tab} and \emph{newline}.+(See the last paragraph in this section for a way to tailor this behavior.)+All other octets are printed in quoted form (as \verb!^<control letter>! or+\verb!\!\verb!<hex code>!).++Darcs has some limited support for locales.+If the system's locale is a single-byte character encoding,+like the Latin encodings,+you can set the environment variable DARCS\_DONT\_ESCAPE\_ISPRINT to 1+and darcs will display all the printables in the current system locale+instead of just the ASCII ones.+NOTE: This curently does not work on some architectures if darcs is+compiled with GHC~6.4 or later. Some non-ASCII control characters might be printed+and can possibly spoof the terminal.++For multi-byte character encodings things are less smooth.+UTF-8 will work if you set DARCS\_DONT\_ESCAPE\_8BIT to 1,+but non-printables outside the 7-bit ASCII range are no longer escaped.+E.g., the extra control characters from Latin-1+might leave your terminal at the mercy of the patch contents.+Space characters outside the 7-bit ASCII range are no longer recognized+and will not be properly escaped at line endings.++As a last resort you can set DARCS\_DONT\_ESCAPE\_ANYTHING to 1.+Then everything that doesn't flip code sets should work,+and so will all the bells and whistles in your terminal.+This environment variable can also be handy+if you pipe the output to a pager or external filter+that knows better than darcs how to handle your encoding.+Note that \emph{all} escaping,+including the special escaping of any line ending spaces,+will be turned off by this setting.++There are two environment variables you can set+to explicitly tell darcs to not escape or escape octets.+They are+DARCS\_DONT\_ESCAPE\_EXTRA and DARCS\_ESCAPE\_EXTRA.+Their values should be strings consisting of the verbatim octets in question.+The do-escapes take precedence over the dont-escapes.+Space characters are still escaped at line endings though.+The special environment variable DARCS\_DONT\_ESCAPE\_TRAILING\_CR+turns off escaping of carriage return last on the line (DOS style).
@@ -0,0 +1,72 @@+-- Copyright (C) 2002-2003 David Roundy+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2, or (at your option)+-- any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; see the file COPYING. If not, write to+-- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+-- Boston, MA 02110-1301, USA.++{-# OPTIONS_GHC -cpp #-}+{-# LANGUAGE CPP #-}++module Main (main) where++import System.IO ( hSetBinaryMode)+import System.IO ( stdin, stdout )+import System.Exit ( exitWith, ExitCode(..) )+import System.Environment ( getArgs )+import Control.Exception ( Exception( AssertionFailed ), handleJust, catchDyn )++import Darcs.RunCommand ( run_the_command )+import Darcs.Flags ( DarcsFlag(Verbose) )+import Darcs.Commands.Help ( help_cmd, list_available_commands, print_version )+import ThisVersion ( darcs_version )+import Darcs.SignalHandler ( withSignalsHandled )+import Context ( context )+import Darcs.Global ( with_atexit )+import Exec ( ExecException(..) )+#include "impossible.h"++assertions :: Control.Exception.Exception -> Maybe String+assertions (AssertionFailed s) = Just s+assertions _ = Nothing++execExceptionHandler :: ExecException -> IO a+execExceptionHandler (ExecException cmd args redirects reason) =+ do putStrLn $ "Failed to execute external command: " ++ unwords (cmd:args) ++ "\n"+ ++ "Lowlevel error: " ++ reason ++ "\n"+ ++ "Redirects: " ++ show redirects ++"\n"+ exitWith $ ExitFailure 3++main :: IO ()+main = with_atexit $ withSignalsHandled $+ flip catchDyn execExceptionHandler $+ handleJust assertions bug $ do+ argv <- getArgs+ case argv of+ -- User called "darcs" without arguments.+ [] -> print_version >> help_cmd [] []+ -- User called "darcs --foo" for some special foo.+ ["-h"] -> help_cmd [] []+ ["--help"] -> help_cmd [] []+ ["--overview"] -> help_cmd [Verbose] []+ ["--commands"] -> list_available_commands+ ["-v"] -> putStrLn darcs_version+ ["--version"] -> putStrLn darcs_version+ ["--exact-version"] -> do+ putStrLn $ "darcs compiled on "++__DATE__++", at "++__TIME__+ putStrLn context+ -- User called a normal darcs command, "darcs foo [args]".+ _ -> do+ hSetBinaryMode stdin True+ hSetBinaryMode stdout True+ run_the_command (head argv) (tail argv)
@@ -0,0 +1,88 @@+\section{Features}++\paragraph{Record changes locally}+In darcs, the equivalent of a cvs ``commit'' is called record, because it+doesn't put the change into any remote or centralized repository. Changes+are always recorded locally, meaning no net access is required in order to+work on your project and record changes as you make them. Moreover, this+means that there is no need for a separate ``disconnected operation'' mode.++\paragraph{Interactive records}+You can choose to perform an interactive record, in which case darcs will+prompt you for each change you have made and ask if you wish to record it.+Of course, you can tell darcs to record all the changes in a given file, or+to skip all the changes in a given file, or go back to a previous change,+or whatever. There is also an experimental graphical interface, which+allows you to view and choose changes even more easily, and in whichever+order you like.++\paragraph{Unrecord local changes}+As a corollary to the ``local'' nature of the record operation, if a change+hasn't yet been published to the world---that is, if the local repository+isn't accessible by others---you can safely unrecord a change (even if it+wasn't the most recently recorded change) and then re-record it+differently, for example if you forgot to add a file, introduced a bug or+realized that what you recorded as a single change was really two separate+changes.++\paragraph{Interactive everything else}+Most darcs commands support an interactive interface. The ``revert''+command, for example, which undoes unrecorded changes has the same+interface as record, so you can easily revert just a single change. Pull,+push, send and apply all allow you to view and interactively select which+changes you wish to pull, push, send or apply.++\paragraph{Test suites}+Darcs has support for integrating a test suite with a repository. If you+choose to use this, you can define a test command (e.g.\ ``make check'') and+have darcs run that command on a clean copy of the project either prior to+recording a change or prior to applying changes---and to reject changes+that cause the test to fail.++\paragraph{Any old server}+Darcs does not require a specialized server in order to make a repository+available for read access. You can use http, ftp, or even just a plain old+ssh server to access your darcs repository.++\paragraph{You decide write permissions}+Darcs doesn't try to manage write access. That's your business. Supported+push methods include direct ssh access (if you're willing to \emph{give}+direct ssh access away), using sudo to allow users who already have shell+access to only apply changes to the repository, or verification of+gpg-signed changes sent by email against a list of allowed keys. In+addition, there is good support for submission of patches by email that+are not automatically applied, but can easily be applied with a shell escape+from a mail reader (this is how I deal with contributions to darcs).++\paragraph{Symmetric repositories}+Every darcs repository is created equal (well, with the exception of a+``partial'' repository, which doesn't contain a full history\ldots), and every+working directory has an associated repository. As a result, there is a+symmetry between ``uploading'' and ``downloading'' changes---you can use+the same commands (push or pull) for either purpose.++\paragraph{CGI script}+Darcs has a CGI script that allows browsing of the repositories.++\paragraph{Portable}+Darcs runs on UNIX (or UNIX-like) systems (which includes Mac~OS~X) as well+as on Microsoft Windows.++\paragraph{File and directory moves}+Renames or moves of files and directories, of course are handled properly,+so when you rename a file or move it to a different directory, its history+is unbroken, and merges with repositories that don't have the file renamed+will work as expected.++\paragraph{Token replace}+You can use the ``darcs replace'' command to modify all occurrences of a+particular token (defined by a configurable set of characters that are+allowed in ``tokens'') in a file. This has the advantage that merges with+changes that introduce new copies of the old token will have the effect of+changing it to the new token---which comes in handy when changing a+variable or function name that is used throughout a project.++\paragraph{Configurable defaults}+You can easily configure the default flags passed to any command on either+a per-repository or a per-user basis or a combination thereof.+
@@ -0,0 +1,187 @@+/*+ * Copyright (C) 2003 David Roundy+ * Most of the UTF code is Copyright (C) 1999-2001 Free Software Foundation, Inc.+ * This file is part of darcs.+ *+ * Darcs is free software; you can redistribute it and/or modify it under+ * the terms of the GNU Library General Public License as published by the+ * Free Software Foundation; either version 2 of the License, or (at your+ * option) any later version.+ *+ * You should have received a copy of the GNU Library General Public+ * License along with the GNU LIBICONV Library; see the file COPYING.LIB.+ * If not, write to the Free Software Foundation, Inc., 51 Franklin Street,+ * Fifth Floor, Boston, MA 02110-1301, USA.+ *+ */+#include "fpstring.h"++#include <stdlib.h>+#include <string.h>+#include <stdio.h>++#ifdef _WIN32+#include <windows.h>+#else+#include <sys/mman.h>+#endif++/* A locale-independent isspace(3) so patches are interpreted the same+ * everywhere. */+// #define ISSPACE(c) \+// ((c) == ' ' || (c) == '\t' || (c) == '\n' || (c) == '\r')++// int first_white(const char *s, int len)+// {+// const char *start;+// const char *end;+// +// for (start = s, end = s + len; s < end && !ISSPACE(*s); s++);+// +// return s - start;+// }++// int first_nonwhite(const char *s, int len)+// {+// const char *start;+// const char *end;+// +// for (start = s, end = s + len; s < end && ISSPACE(*s); s++);+// +// return s - start;+// }++int has_funky_char(const char *s, int len)+{+ // We check first for the more likely \0 so we can break out of+ // memchr that much sooner.+ return !!(memchr(s, 0, len) || memchr(s, 26, len));++}++// ForeignPtr debugging stuff...++static int num_alloced = 0;++void debug_free(void *p) {+ num_alloced--;+ fprintf(stderr, "Freeing %p (%d left)\n", p, num_alloced);+}++void debug_alloc(void *p, const char *name) {+ num_alloced++;+ fprintf(stderr, "Allocating %p named %s (%d left)\n",+ p, name, num_alloced);+}++/* Specification: RFC 2279 */++int utf8_to_ints(HsInt *pwc, const unsigned char *s, int n) {+ /* returns number of unicode chars in the output. The output array is+ assumed to have the same number of elements as the input array, which+ is n. */++ HsInt *pwc_original = pwc;+ while (n > 0) {+ unsigned char c = s[0];++ if (c < 0x80) {+ *pwc++ = c;+ n--;+ s++;+ } else if (c < 0xc2) {+ return -1;+ } else if (c < 0xe0) {+ if (n < 2) return -1;+ if (!((s[1] ^ 0x80) < 0x40)) return -1;+ *pwc++ = ((unsigned) (c & 0x1f) << 6)+ | (unsigned) (s[1] ^ 0x80);+ n -= 2;+ s += 2;+ } else if (c < 0xf0) {+ if (n < 3) return -1;+ if (!((s[1] ^ 0x80) < 0x40 && (s[2] ^ 0x80) < 0x40+ && (c >= 0xe1 || s[1] >= 0xa0)))+ return -1;+ *pwc++ = ((unsigned) (c & 0x0f) << 12)+ | ((unsigned) (s[1] ^ 0x80) << 6)+ | (unsigned) (s[2] ^ 0x80);+ n -= 3;+ s += 3;+ } else if (c < 0xf8 && sizeof(unsigned)*8 >= 32) {+ if (n < 4) return -1;+ if (!((s[1] ^ 0x80) < 0x40 && (s[2] ^ 0x80) < 0x40+ && (s[3] ^ 0x80) < 0x40+ && (c >= 0xf1 || s[1] >= 0x90)))+ return -1;+ *pwc++ = ((unsigned) (c & 0x07) << 18)+ | ((unsigned) (s[1] ^ 0x80) << 12)+ | ((unsigned) (s[2] ^ 0x80) << 6)+ | (unsigned) (s[3] ^ 0x80);+ n -= 4;+ s += 4;+ } else if (c < 0xfc && sizeof(unsigned)*8 >= 32) {+ if (n < 5) return -1;+ if (!((s[1] ^ 0x80) < 0x40 && (s[2] ^ 0x80) < 0x40+ && (s[3] ^ 0x80) < 0x40 && (s[4] ^ 0x80) < 0x40+ && (c >= 0xf9 || s[1] >= 0x88)))+ return -1;+ *pwc++ = ((unsigned) (c & 0x03) << 24)+ | ((unsigned) (s[1] ^ 0x80) << 18)+ | ((unsigned) (s[2] ^ 0x80) << 12)+ | ((unsigned) (s[3] ^ 0x80) << 6)+ | (unsigned) (s[4] ^ 0x80);+ n -= 5;+ s += 5;+ } else if (c < 0xfe && sizeof(unsigned)*8 >= 32) {+ if (n < 6) return -1;+ if (!((s[1] ^ 0x80) < 0x40 && (s[2] ^ 0x80) < 0x40+ && (s[3] ^ 0x80) < 0x40 && (s[4] ^ 0x80) < 0x40+ && (s[5] ^ 0x80) < 0x40+ && (c >= 0xfd || s[1] >= 0x84)))+ return -1;+ *pwc++ = ((unsigned) (c & 0x01) << 30)+ | ((unsigned) (s[1] ^ 0x80) << 24)+ | ((unsigned) (s[2] ^ 0x80) << 18)+ | ((unsigned) (s[3] ^ 0x80) << 12)+ | ((unsigned) (s[4] ^ 0x80) << 6)+ | (unsigned) (s[5] ^ 0x80);+ n -= 6;+ s += 6;+ } else+ return -1;+ }+ return pwc - pwc_original;+}++/* Conversion to and from hex */++void conv_to_hex(unsigned char *dest, unsigned char *from, int num_chars)+{+ static char hex[] = "0123456789abcdef";+ unsigned char *end;++ for (end = from + num_chars; from < end; from++) {+ *dest++ = hex[*from >> 4];+ *dest++ = hex[*from & 0xf];+ }++ return;+}++#define NYBBLE_TO_INT(c) \+ ((c) - ((c) >= 'a' ? 'a' - 10 : '0'))++void conv_from_hex(unsigned char *dest, unsigned char *from, int num_chars)+{+ unsigned char *end;+ unsigned char c;++ end = dest + num_chars;+ while (dest < end) {+ c = NYBBLE_TO_INT(*from) << 4, from++;+ *dest++ = c | NYBBLE_TO_INT(*from), from++;+ }++ return;+}
@@ -0,0 +1,16 @@+#include <HsFFI.h>+#include <sys/types.h>++int wfindps_helper(char c, const char *s, int len);+void debug_free(void *p);+void debug_alloc(void *p, const char *name);++// int first_white(const char *s, int len);+// int first_nonwhite(const char *s, int len);+int has_funky_char(const char *s, int len);++int utf8_to_ints(HsInt *pwc, const unsigned char *s, int n);++void conv_to_hex(unsigned char *dest, unsigned char *from, int num_chars);+void conv_from_hex(unsigned char *dest, unsigned char *from, int num_chars);+
@@ -0,0 +1,11 @@+#ifdef GADT_WITNESSES++#define C(contexts) contexts+#define FORALL(types) forall types.++#else++#define C(contexts)+#define FORALL(types)++#endif
@@ -0,0 +1,414 @@+% This is the FSF GPL, reformatted as an appendix by David Roundy++% This file is intended to be included in another file.++\chapter{The GNU General Public License}+\label{gpl}+\section*{Version 2, June 1991}++\begin{center}+Version 2, June 1991++Copyright \copyright\ 1989, 1991 Free Software Foundation, Inc.++\bigskip++51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA++\bigskip++Everyone is permitted to copy and distribute verbatim copies+of this license document, but changing it is not allowed.++\end{center}++\section*{Preamble}+\begin{quote}+The licenses for most software are designed to take away your freedom to+share and change it. By contrast, the GNU General Public License is+intended to guarantee your freedom to share and change free software---to+make sure the software is free for all its users. This General Public+License applies to most of the Free Software Foundation's software and to+any other program whose authors commit to using it. (Some other Free+Software Foundation software is covered by the GNU Library General Public+License instead.) You can apply it to your programs, too.++When we speak of free software, we are referring to freedom, not price.+Our General Public Licenses are designed to make sure that you have the+freedom to distribute copies of free software (and charge for this service+if you wish), that you receive source code or can get it if you want it,+that you can change the software or use pieces of it in new free programs;+and that you know you can do these things.++To protect your rights, we need to make restrictions that forbid anyone to+deny you these rights or to ask you to surrender the rights. These+restrictions translate to certain responsibilities for you if you+distribute copies of the software, or if you modify it.++For example, if you distribute copies of such a program, whether gratis or+for a fee, you must give the recipients all the rights that you have. You+must make sure that they, too, receive or can get the source code. And+you must show them these terms so they know their rights.++We protect your rights with two steps: (1) copyright the software, and (2)+offer you this license which gives you legal permission to copy,+distribute and/or modify the software.++Also, for each author's protection and ours, we want to make certain that+everyone understands that there is no warranty for this free software. If+the software is modified by someone else and passed on, we want its+recipients to know that what they have is not the original, so that any+problems introduced by others will not reflect on the original authors'+reputations.++Finally, any free program is threatened constantly by software patents.+We wish to avoid the danger that redistributors of a free program will+individually obtain patent licenses, in effect making the program+proprietary. To prevent this, we have made it clear that any patent must+be licensed for everyone's free use or not licensed at all.++The precise terms and conditions for copying, distribution and+modification follow.+\end{quote}++\begin{center}+{\Large \sc GNU General Public License+\\\vspace{3mm}Terms and Conditions For Copying, Distribution and Modification}+\end{center}+++\begin{enumerate}++\addtocounter{enumi}{-1}++\item ++This License applies to any program or other work which contains a notice+placed by the copyright holder saying it may be distributed under the+terms of this General Public License. The ``Program'', below, refers to+any such program or work, and a ``work based on the Program'' means either+the Program or any derivative work under copyright law: that is to say, a+work containing the Program or a portion of it, either verbatim or with+modifications and/or translated into another language. (Hereinafter,+translation is included without limitation in the term ``modification''.)+Each licensee is addressed as ``you''.++Activities other than copying, distribution and modification are not+covered by this License; they are outside its scope. The act of+running the Program is not restricted, and the output from the Program+is covered only if its contents constitute a work based on the+Program (independent of having been made by running the Program).+Whether that is true depends on what the Program does.++\item You may copy and distribute verbatim copies of the Program's source+ code as you receive it, in any medium, provided that you conspicuously+ and appropriately publish on each copy an appropriate copyright notice+ and disclaimer of warranty; keep intact all the notices that refer to+ this License and to the absence of any warranty; and give any other+ recipients of the Program a copy of this License along with the Program.++You may charge a fee for the physical act of transferring a copy, and you+may at your option offer warranty protection in exchange for a fee.++\item++You may modify your copy or copies of the Program or any portion+of it, thus forming a work based on the Program, and copy and+distribute such modifications or work under the terms of Section 1+above, provided that you also meet all of these conditions:++\begin{enumerate}++\item ++You must cause the modified files to carry prominent notices stating that+you changed the files and the date of any change.++\item++You must cause any work that you distribute or publish, that in+whole or in part contains or is derived from the Program or any+part thereof, to be licensed as a whole at no charge to all third+parties under the terms of this License.++\item+If the modified program normally reads commands interactively+when run, you must cause it, when started running for such+interactive use in the most ordinary way, to print or display an+announcement including an appropriate copyright notice and a+notice that there is no warranty (or else, saying that you provide+a warranty) and that users may redistribute the program under+these conditions, and telling the user how to view a copy of this+License. (Exception: if the Program itself is interactive but+does not normally print such an announcement, your work based on+the Program is not required to print an announcement.)++\end{enumerate}+++These requirements apply to the modified work as a whole. If+identifiable sections of that work are not derived from the Program,+and can be reasonably considered independent and separate works in+themselves, then this License, and its terms, do not apply to those+sections when you distribute them as separate works. But when you+distribute the same sections as part of a whole which is a work based+on the Program, the distribution of the whole must be on the terms of+this License, whose permissions for other licensees extend to the+entire whole, and thus to each and every part regardless of who wrote it.++Thus, it is not the intent of this section to claim rights or contest+your rights to work written entirely by you; rather, the intent is to+exercise the right to control the distribution of derivative or+collective works based on the Program.++In addition, mere aggregation of another work not based on the Program+with the Program (or with a work based on the Program) on a volume of+a storage or distribution medium does not bring the other work under+the scope of this License.++\item+You may copy and distribute the Program (or a work based on it,+under Section 2) in object code or executable form under the terms of+Sections 1 and 2 above provided that you also do one of the following:++\begin{enumerate}++\item++Accompany it with the complete corresponding machine-readable+source code, which must be distributed under the terms of Sections+1 and 2 above on a medium customarily used for software interchange; or,++\item++Accompany it with a written offer, valid for at least three+years, to give any third party, for a charge no more than your+cost of physically performing source distribution, a complete+machine-readable copy of the corresponding source code, to be+distributed under the terms of Sections 1 and 2 above on a medium+customarily used for software interchange; or,++\item++Accompany it with the information you received as to the offer+to distribute corresponding source code. (This alternative is+allowed only for noncommercial distribution and only if you+received the program in object code or executable form with such+an offer, in accord with Subsection b above.)++\end{enumerate}+++The source code for a work means the preferred form of the work for+making modifications to it. For an executable work, complete source+code means all the source code for all modules it contains, plus any+associated interface definition files, plus the scripts used to+control compilation and installation of the executable. However, as a+special exception, the source code distributed need not include+anything that is normally distributed (in either source or binary+form) with the major components (compiler, kernel, and so on) of the+operating system on which the executable runs, unless that component+itself accompanies the executable.++If distribution of executable or object code is made by offering+access to copy from a designated place, then offering equivalent+access to copy the source code from the same place counts as+distribution of the source code, even though third parties are not+compelled to copy the source along with the object code.++\item+You may not copy, modify, sublicense, or distribute the Program+except as expressly provided under this License. Any attempt+otherwise to copy, modify, sublicense or distribute the Program is+void, and will automatically terminate your rights under this License.+However, parties who have received copies, or rights, from you under+this License will not have their licenses terminated so long as such+parties remain in full compliance.++\item+You are not required to accept this License, since you have not+signed it. However, nothing else grants you permission to modify or+distribute the Program or its derivative works. These actions are+prohibited by law if you do not accept this License. Therefore, by+modifying or distributing the Program (or any work based on the+Program), you indicate your acceptance of this License to do so, and+all its terms and conditions for copying, distributing or modifying+the Program or works based on it.++\item+Each time you redistribute the Program (or any work based on the+Program), the recipient automatically receives a license from the+original licensor to copy, distribute or modify the Program subject to+these terms and conditions. You may not impose any further+restrictions on the recipients' exercise of the rights granted herein.+You are not responsible for enforcing compliance by third parties to+this License.++\item+If, as a consequence of a court judgment or allegation of patent+infringement or for any other reason (not limited to patent issues),+conditions are imposed on you (whether by court order, agreement or+otherwise) that contradict the conditions of this License, they do not+excuse you from the conditions of this License. If you cannot+distribute so as to satisfy simultaneously your obligations under this+License and any other pertinent obligations, then as a consequence you+may not distribute the Program at all. For example, if a patent+license would not permit royalty-free redistribution of the Program by+all those who receive copies directly or indirectly through you, then+the only way you could satisfy both it and this License would be to+refrain entirely from distribution of the Program.++If any portion of this section is held invalid or unenforceable under+any particular circumstance, the balance of the section is intended to+apply and the section as a whole is intended to apply in other+circumstances.++It is not the purpose of this section to induce you to infringe any+patents or other property right claims or to contest validity of any+such claims; this section has the sole purpose of protecting the+integrity of the free software distribution system, which is+implemented by public license practices. Many people have made+generous contributions to the wide range of software distributed+through that system in reliance on consistent application of that+system; it is up to the author/donor to decide if he or she is willing+to distribute software through any other system and a licensee cannot+impose that choice.++This section is intended to make thoroughly clear what is believed to+be a consequence of the rest of this License.++\item+If the distribution and/or use of the Program is restricted in+certain countries either by patents or by copyrighted interfaces, the+original copyright holder who places the Program under this License+may add an explicit geographical distribution limitation excluding+those countries, so that distribution is permitted only in or among+countries not thus excluded. In such case, this License incorporates+the limitation as if written in the body of this License.++\item+The Free Software Foundation may publish revised and/or new versions+of the General Public License from time to time. Such new versions will+be similar in spirit to the present version, but may differ in detail to+address new problems or concerns.++Each version is given a distinguishing version number. If the Program+specifies a version number of this License which applies to it and ``any+later version'', you have the option of following the terms and conditions+either of that version or of any later version published by the Free+Software Foundation. If the Program does not specify a version number of+this License, you may choose any version ever published by the Free Software+Foundation.++\item+If you wish to incorporate parts of the Program into other free+programs whose distribution conditions are different, write to the author+to ask for permission. For software which is copyrighted by the Free+Software Foundation, write to the Free Software Foundation; we sometimes+make exceptions for this. Our decision will be guided by the two goals+of preserving the free status of all derivatives of our free software and+of promoting the sharing and reuse of software generally.++\begin{center}+{\Large\sc+No Warranty+}+\end{center}++\item+{\sc Because the program is licensed free of charge, there is no warranty+for the program, to the extent permitted by applicable law. Except when+otherwise stated in writing the copyright holders and/or other parties+provide the program ``as is'' without warranty of any kind, either expressed+or implied, including, but not limited to, the implied warranties of+merchantability and fitness for a particular purpose. The entire risk as+to the quality and performance of the program is with you. Should the+program prove defective, you assume the cost of all necessary servicing,+repair or correction.}++\item+{\sc In no event unless required by applicable law or agreed to in writing+will any copyright holder, or any other party who may modify and/or+redistribute the program as permitted above, be liable to you for damages,+including any general, special, incidental or consequential damages arising+out of the use or inability to use the program (including but not limited+to loss of data or data being rendered inaccurate or losses sustained by+you or third parties or a failure of the program to operate with any other+programs), even if such holder or other party has been advised of the+possibility of such damages.}++\end{enumerate}+++\begin{center}+{\Large\sc End of Terms and Conditions}+\end{center}++\section{Appendix: How to Apply These Terms to Your New Programs}++If you develop a new program, and you want it to be of the greatest+possible use to the public, the best way to achieve this is to make it+free software which everyone can redistribute and change under these+terms.++ To do so, attach the following notices to the program. It is safest to+ attach them to the start of each source file to most effectively convey+ the exclusion of warranty; and each file should have at least the+ ``copyright'' line and a pointer to where the full notice is found.++\begin{quote}+$<$one line to give the program's name and a brief idea of what it does.$>$ \\+Copyright (C) $<$year$>$ $<$name of author$>$ \\++This program is free software; you can redistribute it and/or modify+it under the terms of the GNU General Public License as published by+the Free Software Foundation; either version 2 of the License, or+(at your option) any later version.++This program is distributed in the hope that it will be useful,+but WITHOUT ANY WARRANTY; without even the implied warranty of+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+GNU General Public License for more details.++You should have received a copy of the GNU General Public License+along with this program; if not, write to the Free Software+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.+\end{quote}++Also add information on how to contact you by electronic and paper mail.++If the program is interactive, make it output a short notice like this+when it starts in an interactive mode:++\begin{quote}+Gnomovision version 69, Copyright (C) $<$year$>$ $<$name of author$>$ \\+Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. \\+This is free software, and you are welcome to redistribute it+under certain conditions; type `show c' for details.+\end{quote}+++The hypothetical commands {\tt show w} and {\tt show c} should show the+appropriate parts of the General Public License. Of course, the commands+you use may be called something other than {\tt show w} and {\tt show c};+they could even be mouse-clicks or menu items---whatever suits your+program.++You should also get your employer (if you work as a programmer) or your+school, if any, to sign a ``copyright disclaimer'' for the program, if+necessary. Here is a sample; alter the names:++\begin{quote}+Yoyodyne, Inc., hereby disclaims all copyright interest in the program \\+`Gnomovision' (which makes passes at compilers) written by James Hacker. \\++$<$signature of Ty Coon$>$, 1 April 1989 \\+Ty Coon, President of Vice+\end{quote}+++This General Public License does not permit incorporating your program+into proprietary programs. If your program is a subroutine library, you+may consider it more useful to permit linking proprietary applications+with the library. If this is what you want to do, use the GNU Library+General Public License instead of this License.
@@ -0,0 +1,305 @@+#ifdef HAVE_CURL++#include "hscurl.h"++#include <curl/curl.h>+#include <errno.h>+#include <stdio.h>+#include <stdlib.h>+#include <string.h>++enum RESULT_CODES+ {+ RESULT_OK = 0,+ RESULT_MALLOC_FAIL,+ RESULT_SELECT_FAIL,+ RESULT_MULTI_INIT_FAIL,+ RESULT_EASY_INIT_FAIL,+ RESULT_SLIST_APPEND_FAIL,+ RESULT_NO_RUNNING_HANDLES,+ RESULT_MULTI_INFO_READ_FAIL,+ RESULT_UNKNOWN_MESSAGE,+ RESULT_FILE_OPEN_FAIL+ };++static const char *error_strings[] =+ {+ "",+ "malloc() failed",+ "select() failed",+ "curl_multi_init() failed",+ "curl_easy_init() failed",+ "curl_slist_append() failed",+ "curl_multi_perform() - no running handles",+ "curl_multi_info_read() failed",+ "curl_multi_info_read() returned unknown message",+ "fopen() failed"+ };++struct UrlData+{+ char *url;+ FILE *file;+ struct curl_slist *headers;+};++static int debug = 0;+static const char user_agent[] =+ "darcs/" PACKAGE_VERSION " libcurl/" LIBCURL_VERSION;+static const char *proxypass;+static int init_done = 0;+static CURLM *multi = NULL;+static int msgs_in_queue = 0;+static char *last_url = NULL;++static const char *perform()+{+ int error;+ int running_handles, running_handles_last;+ fd_set fd_read, fd_write, fd_except;+ int max_fd;+ long timeout;+ struct timeval tval;++ error = curl_multi_perform(multi, &running_handles);+ if (error != CURLM_OK && error != CURLM_CALL_MULTI_PERFORM)+ return curl_multi_strerror(error);+ if (running_handles == 0)+ return error_strings[RESULT_NO_RUNNING_HANDLES];++ running_handles_last = running_handles;+ while (1)+ {+ while (error == CURLM_CALL_MULTI_PERFORM)+ error = curl_multi_perform(multi, &running_handles);++ if (error != CURLM_OK)+ return curl_multi_strerror(error);++ if (running_handles < running_handles_last)+ break;++ FD_ZERO(&fd_read);+ FD_ZERO(&fd_write);+ FD_ZERO(&fd_except);++ error = curl_multi_fdset(multi, &fd_read, &fd_write, &fd_except, &max_fd);+ if (error != CURLM_OK && error != CURLM_CALL_MULTI_PERFORM)+ return curl_multi_strerror(error);++#ifdef CURL_MULTI_TIMEOUT+ error = curl_multi_timeout(multi, &timeout);+ if (error != CURLM_OK && error != CURLM_CALL_MULTI_PERFORM)+ return curl_multi_strerror(error);++ if (timeout == -1)+#endif+ timeout = 100;++ tval.tv_sec = timeout / 1000;+ tval.tv_usec = timeout % 1000 * 1000;++ while (select(max_fd + 1, &fd_read, &fd_write, &fd_except, &tval) < 0)+ if (errno != EINTR)+ {+ if (debug)+ perror(error_strings[RESULT_SELECT_FAIL]);+ return error_strings[RESULT_SELECT_FAIL];+ }++ error = CURLM_CALL_MULTI_PERFORM;+ }++ return NULL;+}++const char *curl_request_url(const char *url,+ const char *filename,+ int cache_time)+{+ int error;++ if (init_done == 0)+ {+ error = curl_global_init(CURL_GLOBAL_ALL);+ if (error != CURLE_OK)+ return curl_easy_strerror(error);+ proxypass = getenv("DARCS_PROXYUSERPWD");+ init_done = 1;+ }++ if (multi == NULL)+ {+ multi = curl_multi_init();+ if (multi == NULL)+ return error_strings[RESULT_MULTI_INIT_FAIL];+#ifdef CURL_PIPELINING+ error = curl_multi_setopt(multi, CURLMOPT_PIPELINING, 1);+ if (error != CURLM_OK && error != CURLM_CALL_MULTI_PERFORM)+ return curl_multi_strerror(error);+#endif+ }++ CURL *easy = curl_easy_init();+ if (easy == NULL)+ return error_strings[RESULT_EASY_INIT_FAIL];++ if (debug)+ {+ error = curl_easy_setopt(easy, CURLOPT_VERBOSE, 1);+ if (error != CURLE_OK)+ return curl_easy_strerror(error);+ }++ struct UrlData *url_data = malloc(sizeof(struct UrlData));+ if (url_data == NULL)+ return error_strings[RESULT_MALLOC_FAIL];++ url_data->url = strdup(url);+ if (url_data->url == NULL)+ return error_strings[RESULT_MALLOC_FAIL];++ url_data->file = fopen(filename,"wb");+ if (url_data->file == NULL)+ {+ if (debug)+ perror(error_strings[RESULT_FILE_OPEN_FAIL]);+ return error_strings[RESULT_FILE_OPEN_FAIL];+ }++ error = curl_easy_setopt(easy, CURLOPT_PRIVATE, url_data);+ if (error != CURLE_OK)+ return curl_easy_strerror(error);++ error = curl_easy_setopt(easy, CURLOPT_URL, url_data->url);+ if (error != CURLE_OK)+ return curl_easy_strerror(error);++#ifdef CURLOPT_WRITEDATA+ error = curl_easy_setopt(easy, CURLOPT_WRITEDATA, url_data->file);+#else+ error = curl_easy_setopt(easy, CURLOPT_FILE, url_data->file);+#endif+ if (error != CURLE_OK)+ return curl_easy_strerror(error);++ error = curl_easy_setopt(easy, CURLOPT_USERAGENT, user_agent);+ if (error != CURLE_OK)+ return curl_easy_strerror(error);++ error = curl_easy_setopt(easy, CURLOPT_FOLLOWLOCATION, 1);+ if (error != CURLE_OK)+ return curl_easy_strerror(error);++ error = curl_easy_setopt(easy, CURLOPT_FAILONERROR, 1);+ if (error != CURLE_OK)+ return curl_easy_strerror(error);++ error = curl_easy_setopt(easy, CURLOPT_HTTPAUTH, CURLAUTH_ANY);+ if (error != CURLE_OK)+ return curl_easy_strerror(error);++ /* libcurl currently always sends Pragma: no-cache, but never+ Cache-Control, which is contradictory. We override both, just to+ be sure. */+ url_data->headers = curl_slist_append(NULL, "Accept: */*");+ if(cache_time == 0)+ {+ url_data->headers =+ curl_slist_append(url_data->headers, "Pragma: no-cache");+ url_data->headers =+ curl_slist_append(url_data->headers, "Cache-Control: no-cache");+ }+ else if(cache_time > 0)+ {+ /* This won't work well with HTTP/1.0 proxies. */+ char buf[40];+ snprintf(buf, sizeof(buf), "Cache-Control: max-age=%d", cache_time);+ buf[sizeof(buf) - 1] = '\n';+ url_data->headers = curl_slist_append(url_data->headers, "Pragma:");+ url_data->headers = curl_slist_append(url_data->headers, buf);+ }+ else+ {+ url_data->headers = curl_slist_append(url_data->headers, "Pragma:");+ url_data->headers = curl_slist_append(url_data->headers, "Cache-Control:");+ }+ if (url_data->headers == NULL)+ return error_strings[RESULT_SLIST_APPEND_FAIL];++ error = curl_easy_setopt(easy, CURLOPT_HTTPHEADER, url_data->headers);+ if (error != CURLE_OK)+ return curl_easy_strerror(error);++ if (proxypass && *proxypass)+ {+ error = curl_easy_setopt(easy, CURLOPT_PROXYUSERPWD, proxypass);+ if (error != CURLE_OK)+ return curl_easy_strerror(error);+ }++ error = curl_multi_add_handle(multi, easy);+ if (error != CURLM_OK && error != CURLM_CALL_MULTI_PERFORM)+ return curl_multi_strerror(error);++ return error_strings[RESULT_OK];+}++const char *curl_wait_next_url()+{+ if (last_url != NULL)+ {+ free(last_url);+ last_url = NULL;+ }++ if (msgs_in_queue == 0)+ {+ const char *error = perform();+ if (error != NULL)+ return error;+ }++ CURLMsg *msg = curl_multi_info_read(multi, &msgs_in_queue);+ if (msg == NULL)+ return error_strings[RESULT_MULTI_INFO_READ_FAIL];++ if (msg->msg == CURLMSG_DONE)+ {+ CURL *easy = msg->easy_handle;+ CURLcode result = msg->data.result;+ struct UrlData *url_data;+ int error = curl_easy_getinfo(easy, CURLINFO_PRIVATE, (char **)&url_data);+ if (error != CURLE_OK)+ return curl_easy_strerror(error);++ last_url = url_data->url;+ fclose(url_data->file);+ curl_slist_free_all(url_data->headers);+ free(url_data);++ error = curl_multi_remove_handle(multi, easy);+ if (error != CURLM_OK && error != CURLM_CALL_MULTI_PERFORM)+ return curl_multi_strerror(error);+ curl_easy_cleanup(easy);++ if (result != CURLE_OK)+ return curl_easy_strerror(result);+ }+ else+ return error_strings[RESULT_UNKNOWN_MESSAGE];++ return error_strings[RESULT_OK];+}++const char *curl_last_url()+{+ return last_url != NULL ? last_url : "";+}++void curl_enable_debug()+{+ debug = 1;+}++#endif
@@ -0,0 +1,9 @@+const char *curl_request_url(const char *url,+ const char *filename,+ int cache_time);++const char *curl_wait_next_url();++const char *curl_last_url();++void curl_enable_debug();
@@ -0,0 +1,8 @@+import qualified Darcs.Bug as Bug_++#define darcsBug (\imp_funny_name -> imp_funny_name (__FILE__,__LINE__,__TIME__,__DATE__))++#define bug (darcsBug Bug_._bug)+#define impossible (darcsBug Bug_._impossible)+#define fromJust (darcsBug Bug_._fromJust)+#define bugDoc (darcsBug Bug_._bugDoc)
@@ -0,0 +1,173 @@+/*+ Copyright (C) 2005 Juliusz Chroboczek++ This program is free software; you can redistribute it and/or modify+ it under the terms of the GNU General Public License as published by+ the Free Software Foundation; either version 2, or (at your option)+ any later version.++ This program is distributed in the hope that it will be useful,+ but WITHOUT ANY WARRANTY; without even the implied warranty of+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+ GNU General Public License for more details.++ You should have received a copy of the GNU General Public License+ along with this program; see the file COPYING. If not, write to+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+ Boston, MA 02110-1301, USA.+*/++#include <sys/types.h>+#include <sys/stat.h>+#include <fcntl.h>+#include <string.h>+#include <stdio.h>+#include <stdlib.h>++#include <unistd.h>+#include <errno.h>+#include <sys/time.h>++#ifdef _WIN32+int+maybe_relink(const char *src, const char *dst, int careful)+{+ return 0;+}++#else++/* Tries to link src to dst if both files exist and have the same+ contents. If careful is false only the file sizes are compared; if+ it is true, the full contents are compared.++ This code assumes that dst cannot change behind our back -- the+ caller is supposed to protect it by a lock. On the other hand, it+ does handle simultaneous access to src, but only if src is never+ modified in place. It should also be safe over NFS.++ Assumes that rename cannot fail mid-way on a single filesystem.++ Returns 1 on success, 0 if the files are already linked, -1 for an+ error in errno, -2 if the files cannot be linked because they are not+ the same, on different devices, or on a filesystem with no support for+ hard links, -3 if there was a race condition, -4 if something unexpected+ happened. */++int+maybe_relink(char *src, char *dst, int careful)+{+#define RELINK_BUFFER_SIZE 8192++ int len, rc, saved_errno;+ char *tempname;+ struct stat srcstat, dststat, tempstat;+ struct timeval now;++ rc = stat(src, &srcstat);+ if(rc < 0) {+ if(errno == ENOENT)+ return -2;+ else+ return -1;+ }++ rc = stat(dst, &dststat);+ if(rc < 0) return -1;++ if(!S_ISREG(srcstat.st_mode) || !S_ISREG(dststat.st_mode)) {+ return -4;+ }++ if(srcstat.st_dev != dststat.st_dev) {+ return -2;+ }++ if(srcstat.st_ino == dststat.st_ino)+ /* Files are already linked */+ return 0;++ if(srcstat.st_size != dststat.st_size)+ return -2;++ /* link is atomic even on NFS, we will fail gracefully if the name+ is not unique. */+ gettimeofday(&now, NULL);+ rc = strlen(dst) + 6;+ tempname = malloc(rc);+ if(tempname == NULL) return -1;+ len = snprintf(tempname, rc, "%s-%04x", dst,+ ((unsigned)(now.tv_usec ^ (now.tv_usec >> 16))) & 0xFFFF);+ if(len < 0 || len >= rc) {+ free(tempname);+ return -4;+ }++ rc = link(src, tempname);+ if(rc < 0) {+ /* We need to try to remove the link in case this was a+ problem with NFS over an unreliable transport. */+ goto fail;+ }++ rc = stat(tempname, &tempstat);+ if(rc < 0) goto fail;++ /* Check for a race condition. The size and mtime checks are+ gratuitious, but they don't cost much, and might save your data+ if you're on a filesystem without i-nodes. */+ if(tempstat.st_ino != srcstat.st_ino ||+ tempstat.st_size != srcstat.st_size ||+ tempstat.st_mtime != srcstat.st_mtime) {+ unlink(tempname);+ free(tempname);+ return -3;+ }+ if(careful) {+ int fd1, fd2, i, rc1, rc2;+ char buf1[RELINK_BUFFER_SIZE], buf2[RELINK_BUFFER_SIZE];++ fd1 = open(tempname, O_RDONLY);+ if(fd1 < 0) goto fail;+ fd2 = open(dst, O_RDONLY);+ if(fd2 < 0) { close(fd1); goto fail; }++ i = 0;+ /* This comparison is approximate: it doesn't deal with short+ reads and EINTR. It's okay, as these cases are rare and if+ they happen, we're still safe. */+ while(i < tempstat.st_size) {+ rc1 = read(fd1, buf1, RELINK_BUFFER_SIZE);+ if(rc1 < 0) { close(fd1); close(fd2); goto fail; }+ rc2 = read(fd2, buf2, RELINK_BUFFER_SIZE);+ if(rc2 < 0) { close(fd1); close(fd2); goto fail; }+ if(rc1 == 0 || rc1 != rc2 || memcmp(buf1, buf2, rc1) != 0) {+ close(fd1); close(fd2);+ unlink(tempname);+ free(tempname);+ return -2;+ }+ i += rc1;+ }+ close(fd1); close(fd2);+ }++ rc = rename(tempname, dst);+ if(rc < 0) goto fail;++ free(tempname);+ return 1;++ fail:+ saved_errno = errno;+ unlink(tempname);+ free(tempname);+ errno = saved_errno;+ if(errno == EPERM || errno == EOPNOTSUPP)+ return -2;+ return -1;++#undef RELINK_BUFFER_SIZE+}++#endif
@@ -0,0 +1,1 @@+int maybe_relink(const char *src, const char *dst, int careful);
@@ -0,0 +1,184 @@+-- | This program mangles a pseudo-LaTeX document into actual LaTeX.+-- There are three key changes to the input:+--+-- * \\input{foo} is replaced by the contents of the file foo (after+-- it, too, is mangled). Note that this is relative to the+-- working directory, *not* relative to the file being parsed.+--+-- * Anything between \\begin{code} and \\end{code} is deleted.+-- Note that this is quite unlike normal literate documentation+-- (for which we use Haddock, not LaTeX).+--+-- * Some nonstandard pseudo-LaTeX commands are expanded into actual+-- LaTeX text. In particular, \\darcsCommand{foo} is replaced by+-- LaTeX markup describing the command @foo@.+module Main (main) where+import System.FilePath ( (</>) )+import System.Environment ( getArgs )+import System.Exit ( exitWith, ExitCode(..) )+import Text.Regex ( matchRegex, mkRegex )+import Darcs.Commands ( DarcsCommand(SuperCommand,+ command_sub_commands, command_name,+ command_extra_arg_help, command_basic_options,+ command_advanced_options, command_help,+ command_description),+ extract_commands )+import Darcs.Arguments ( options_latex )+import Darcs.Commands.Help ( command_control_list, environmentHelp )+import English ( andClauses )+import ThisVersion ( darcs_version )++the_commands :: [DarcsCommand]+the_commands = extract_commands command_control_list++-- | The entry point for this program. The path to the TeX master+-- file is supplied as the first argument. Bootstrapping into+-- 'preproc' then happens by passing it a pseudo-document that+-- contains a single input (include) line.+main :: IO ()+main = do+ args <- getArgs+ if length args < 1+ then exitWith $ ExitFailure 1+ else return ()+ putStrLn "%% This file was automatically generated by preproc."+ c <- preproc ["\\input{"++head args++"}"]+ mapM_ putStrLn c++-- | Depending on whether pdflatex or htlatex is to be used, the LaTeX+-- output of this program must vary subtly. This procedure returns+-- true iff the command-line arguments contain @--html@.+am_html :: IO Bool+am_html = do args <- getArgs+ return $ elem "--html" args++-- | Given a list of input lines in pseudo-LaTeX, return the same+-- document in LaTeX. The pseudo-LaTeX lines are replaced, other+-- lines are used unmodified.+preproc :: [String] -> IO [String]+preproc [] = return [] -- Empty input, empty output.+preproc ("\\usepackage{html}":ss) = -- only use html package with latex2html+ do rest <- preproc ss+ ah <- am_html+ if ah then return $ "\\usepackage{html}" : rest+ else return $ "\\usepackage{hyperref}" : rest+preproc ("\\begin{code}":ss) = ignore ss+ where ignore :: [String] -> IO [String]+ ignore ("\\end{code}":ss') = preproc ss'+ ignore (_:ss') = ignore ss'+ ignore [] = return []+preproc ("\\begin{options}":ss) =+ do rest <- preproc ss+ ah <- am_html+ if ah then return $ "\\begin{rawhtml}" : "<div class=\"cmd-opt-hdr\">" : rest+ else return $ ("\\begin{Verbatim}[frame=lines,xleftmargin=1cm," +++ "xrightmargin=1cm]") : rest+preproc ("\\end{options}":ss) =+ do rest <- preproc ss+ ah <- am_html+ if ah then return $ "</div>" : "\\end{rawhtml}" : rest+ else return $ "\\end{Verbatim}" : rest+preproc ("\\darcsVersion":ss) = do+ rest <- preproc ss+ return $ darcs_version:rest+preproc (s:ss) = do+ rest <- preproc ss+ let rx = mkRegex "^\\\\(input|darcs(Command|Env))\\{(.+)\\}$"+ case matchRegex rx s of+ Just ["input", _, path] ->+ do cs <- readFile $ "src" </> path -- ratify readFile: not part of darcs executable+ this <- preproc $ lines cs+ return $ this ++ rest+ Just ["darcsCommand", _, command] ->+ return $ commandHelp command : rest+ Just ["darcsEnv", _, variable] ->+ return $ envHelp variable : rest+ -- The base case for the whole preproc function. Nothing to+ -- mangle, so this is an ordinary line of TeX, and we append it to+ -- the result unmodified.+ _ -> return $ s : rest++commandHelp :: String -> String+commandHelp command = section ++ "{darcs " ++ command ++ "}\n" +++ "\\label{" ++ command ++ "}\n" +++ gh ++ get_options command ++ gd+ where+ section = if ' ' `elem` command then "\\subsubsection" else "\\subsection"+ -- | Given a Darcs command name as a string, return that command's (multi-line) help string.+ gh :: String+ gh = escape_latex_specials $ command_property command_help the_commands command+ -- | Given a Darcs command name as a string, return that command's (one-line) description string.+ gd :: String+ gd = command_property command_description the_commands command++get_options :: String -> String+get_options comm = get_com_options $ get_c names the_commands+ where names = words comm++get_c :: [String] -> [DarcsCommand] -> [DarcsCommand]+get_c (name:ns) commands =+ case ns of+ [] -> [get name commands]+ _ -> case get name commands of+ c@SuperCommand { } ->+ c:(get_c ns $ extract_commands $ command_sub_commands c)+ _ ->+ error $ "Not a supercommand: " ++ name+ where get n (c:cs) | command_name c == n = c+ | otherwise = get n cs+ get n [] = error $ "No such command: "++n+get_c [] _ = error "no command specified"++get_com_options :: [DarcsCommand] -> String+get_com_options c =+ "\\par\\verb!Usage: darcs " ++ cmd ++ " [OPTION]... " +++ args ++ "!\n\n" ++ "Options:\n\n" ++ options_latex opts1 +++ (if null opts2 then "" else "\n\n" ++ "Advanced options:\n\n" ++ options_latex opts2)+ where cmd = unwords $ map command_name c+ args = unwords $ command_extra_arg_help $ last c+ opts1 = command_basic_options $ last c+ opts2 = command_advanced_options $ last c++command_property :: (DarcsCommand -> String) -> [DarcsCommand] -> String+ -> String+command_property property commands name =+ property $ last c+ where names = words name+ c = get_c names commands++++envHelp :: String -> String+envHelp var = unlines $ render $ entry environmentHelp+ where render (ks, ds) =+ ("\\paragraph{" ++ escape_latex_specials (andClauses ks) ++ "}") :+ ("\\label{env:" ++ var ++ "}") :+ map escape_latex_specials ds+ entry [] = undefined+ entry (x:xs) | elem var $ fst x = x+ | otherwise = entry xs++-- | LaTeX treats a number of characters or sequences specially.+-- Therefore when including ordinary help text in a LaTeX document, it+-- is necessary to escape these characters in the way LaTeX expects.+escape_latex_specials :: String -> String+-- Order is important+escape_latex_specials =+ (bs2 . amp . percent . carrot . dollar . underscore . rbrace . lbrace . bs1)+ where+ amp = replace "&" "\\&"+ bs1 = replace "\\" "\001"+ bs2 = replace "\001" "$\\backslash$"+ carrot = replace "^" "\\^{}"+ dollar = replace "$" "\\$"+ lbrace = replace "{" "\\{"+ percent = replace "%" "\\%"+ rbrace = replace "}" "\\}"+ underscore = replace "_" "\\_"++ replace :: Eq a => [a] -> [a] -> [a] -> [a]+ replace _ _ [] = []+ replace find repl s =+ if take (length find) s == find+ then repl ++ (replace find repl (drop (length find) s))+ else [head s] ++ replace find repl (tail s)
@@ -0,0 +1,147 @@+\section{Switching from CVS}++Darcs is refreshingly different from CVS.++CVS keeps version controlled data in a central repository, and+requires that users check out a working directory whenever they wish+to access the version-controlled sources. In order to modify the+central repository, a user needs to have write access to the central+repository; if he doesn't, CVS merely becomes a tool to get the latest+sources.++In darcs there is no distinction between working directories and+repositories. In order to work on a project, a user makes a local+copy of the repository he wants to work in; he may then harness the+full power of version control locally. In order to distribute his+changes, a user who has write access can \emph{push} them to the+remote repository; one who doesn't can simply send them by e-mail in a+format that makes them easy to apply on the remote system.++\paragraph{Darcs commands for CVS users}+Because of the different models used by cvs and darcs, it is+difficult to provide a complete equivalence between cvs and darcs.+A rough correspondence for the everyday commands follows:\\+% The \hspace commands force a line break in the postscript output,+% but they are effectively ignored in the html output.+\begin{description}\addtolength{\itemsep}{-0.5\baselineskip}+\item[{\tt cvs checkout}\hspace*{100em}]+ {\tt darcs get}+\item[{\tt cvs update}\hspace*{100em}]+ {\tt darcs pull}+\item[{\tt cvs -n update}\hspace*{100em}]+ {\tt darcs pull \verb!--dry-run!}+ (summarize remote changes)+\item[{\tt cvs -n update}\hspace*{100em}]+ {\tt darcs whatsnew \verb!--summary!}+ (summarize local changes)+\item[{\tt cvs -n update | grep '?'}\hspace*{100em}]+ {\tt darcs whatsnew -ls | grep \verb!^a! }+ (list potential files to add)+\item[{\tt rm foo.txt; cvs update foo.txt}\hspace*{100em}]+ {\tt darcs revert foo.txt}+ (revert to foo.txt from repo)+\item[{\tt cvs diff}\hspace*{100em}]+ {\tt darcs whatsnew}+ (if checking local changes)+\item[{\tt cvs diff}\hspace*{100em}]+ {\tt darcs diff}+ (if checking recorded changes)+\item[{\tt cvs commit}\hspace*{100em}]+ {\tt darcs record}+ (if committing locally)+\item[{\tt cvs commit}\hspace*{100em}]+ {\tt darcs tag}+ (if marking a version for later use)+\item[{\tt cvs commit}\hspace*{100em}]+ {\tt darcs push} or {\tt darcs send}+ (if committing remotely)+\item[{\tt cvs diff | mail}\hspace*{100em}]+ {\tt darcs send}+\item[{\tt cvs add}\hspace*{100em}]+ {\tt darcs add}+\item[{\tt cvs tag -b}\hspace*{100em}]+ {\tt darcs get}+\item[{\tt cvs tag}\hspace*{100em}]+ {\tt darcs tag}+\end{description}++\paragraph{Migrating CVS repositories to darcs}++Tools and instructions for migrating CVS repositories to darcs are provided+on the darcs community website: +\htmladdnormallinkfoot{http://wiki.darcs.net/DarcsWiki/ConvertingFromCvs}{http://wiki.darcs.net/DarcsWiki/ConvertingFromCvs}+++\section{Switching from arch}++Although arch, like darcs, is a distributed system, and the two systems+have many similarities (both require no special server, for example), their+essential organization is very different.++Like CVS, arch keeps data in two types of data structures:+repositories (called ``archives'') and working directories. In order+to modify a repository, one must first check out a corresponding+working directory. This requires that users remember a number of+different ways of pushing data around --- \verb|tla| \verb|get|,+\verb|update|, \verb|commit|, \verb|archive-mirror| and so on.++In darcs, on the other hand, there is no distinction between working+directories and repositories, and just checking out your sources+creates a local copy of a repository. This allows you to harness the+full power of version control in any scratch copy of your sources, and+also means that there are just two ways to push data around:+\verb|darcs| \verb|record|, which stores edits into your local+repository, and \verb|pull|, which moves data between repositories.+(\verb|darcs| \verb|push| is merely the opposite of \verb|pull|;+\verb|send| and \verb|apply| are just the two halves of \verb|push|).++\paragraph{Darcs commands for arch users}++Because of the different models used by arch and darcs, it is+difficult to provide a complete equivalence between arch and darcs.+A rough correspondence for the everyday commands follows:\\+\begin{description}\addtolength{\itemsep}{-0.5\baselineskip}+\item[{\tt tla init-tree}\hspace*{100em}]+ {\tt darcs initialize}+\item[{\tt tla get}\hspace*{100em}]+ {\tt darcs get}+\item[{\tt tla update}\hspace*{100em}]+ {\tt darcs pull}+\item[{\tt tla file-diffs f | patch -R }\hspace*{100em}]+ {\tt darcs revert}+\item[{\tt tla changes --diffs}\hspace*{100em}]+ {\tt darcs whatsnew}+\item[{\tt tla logs}\hspace*{100em}]+ {\tt darcs changes}+\item[{\tt tla file-diffs}\hspace*{100em}]+ {\tt darcs diff -u}+\item[{\tt tla add}\hspace*{100em}]+ {\tt darcs add}+\item[{\tt tla mv}\hspace*{100em}]+ {\tt darcs move}+ (not {\tt tla move})+\item[{\tt tla commit}\hspace*{100em}]+ {\tt darcs record}+ (if committing locally)+\item[{\tt tla commit}\hspace*{100em}]+ {\tt darcs tag}+ (if marking a version for later use)+\item[{\tt tla commit}\hspace*{100em}]+ {\tt darcs push} or {\tt darcs send}+ (if committing remotely)+\item[{\tt tla archive-mirror}\hspace*{100em}]+ {\tt darcs pull} or {\tt darcs push}+\item[{\tt tla tag}\hspace*{100em}]+ {\tt darcs get}+ (if creating a branch)+\item[{\tt tla tag}\hspace*{100em}]+ {\tt darcs tag}+ (if creating a tag).+\end{description}++\paragraph{Migrating arch repositories to darcs}++Tools and instructions for migrating arch repositories to darcs are provided+on the darcs community website: +\htmladdnormallinkfoot{http://wiki.darcs.net/DarcsWiki/ConvertingFromArch}{http://wiki.darcs.net/DarcsWiki/ConvertingFromArch}+
@@ -0,0 +1,38 @@+#include <sys/types.h>+#include <sys/stat.h>+#include <stdlib.h>+#include <errno.h>+#include "umask.h"++int+set_umask(char *mask_string)+{+#ifndef WIN32+ int rc;+ unsigned mask;+ char *end;++ mask = strtoul(mask_string, &end, 8);+ if(!end || *end != '\0') {+ errno = EINVAL;+ return -1;+ }++ rc = umask(mask);+ return rc;+#else+ /* umask() has no useful meaning on win32. */+ return 0;+#endif /* #ifndef WIN32 ... else ... */+}++int+reset_umask(int old_mask)+{+#ifndef WIN32+ umask(old_mask);+ return 1;+#else+ return 0;+#endif /* #ifndef WIN32 ... else ... */+}
@@ -0,0 +1,2 @@+int set_umask(char *mask_string);+int reset_umask(int old_mask);
@@ -0,0 +1,822 @@+% Copyright (C) 2002-2005,2007 David Roundy+%+% This program is free software; you can redistribute it and/or modify+% it under the terms of the GNU General Public License as published by+% the Free Software Foundation; either version 2, or (at your option)+% any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; see the file COPYING. If not, write to+% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,+% Boston, MA 02110-1301, USA.++\documentclass{report}+\usepackage{color}++\usepackage{verbatim}+\newenvironment{code}{\color{blue}\verbatim}{\endverbatim}++\begin{document}++% Definition of title page:+\title{+ Unit Testing for darcs in Haskell+}+\author{+ David Roundy % insert author(s) here+}++\maketitle++\tableofcontents % Table of Contents++\chapter{Introduction}++This is a unit testing program, which is intended to make sure that all the+functions of my darcs code work properly.++\begin{code}+{-# OPTIONS_GHC -cpp -fno-warn-orphans -fno-warn-deprecations -fglasgow-exts #-}+{-# LANGUAGE CPP #-}++module Main (main) where++import System.IO.Unsafe ( unsafePerformIO )+import ByteStringUtils hiding ( intercalate )+import qualified Data.ByteString.Char8 as BC ( unpack, pack )+import qualified Data.ByteString as B ( empty, concat, length, unpack, foldr,+ cons, ByteString, null, filter, head )+import Data.Char ( isPrint )+import Darcs.Patch+import Darcs.Patch.Test+import Darcs.Patch.Unit ( patch_unit_tests )+import Lcs ( shiftBoundaries )+import Test.QuickCheck+import Printer ( renderPS, text )+import Darcs.Patch.Commute+import Data.Array.Base+import Data.Array.Unboxed+import Control.Monad.ST+import Darcs.Ordered+import Darcs.Sealed ( Sealed(Sealed), unsafeUnseal )+import Darcs.Email ( make_email, read_email, formatHeader )+import Test.HUnit ( assertBool, assertFailure )+import Test.Framework.Providers.QuickCheck2 ( testProperty )+import Test.Framework.Providers.HUnit ( testCase )+import Test.Framework.Runners.Console ( defaultMain )+import Test.Framework ( Test )++#include "impossible.h"+\end{code}++\chapter{Main body of code}++\begin{code}+main :: IO ()+main = do+ putStr ("There are a total of "++(show (length primitive_test_patches))+ ++" primitive patches.\n")+ putStr ("There are a total of "+++ (show (length test_patches))++" patches.\n")+ defaultMain tests++-- | Utility function to run bools with test-framework+testBool :: String -> Bool -> Test+testBool name test = testCase name (assertBool assertName test)+ where assertName = "boolean test \"" ++ name ++ "\" should return True"++-- | Utility function to run old tests that return a list of error messages,+-- with the empty list meaning success.+testStringList :: String -> [String] -> Test+testStringList name test = testCase name $ mapM_ assertFailure test++-- | This is the big list of tests that will be run using testrunner.+tests :: [Test]+tests = patch_unit_tests +++ [testBool "Checking that UTF-8 packing and unpacking preserves 'hello world'"+ (unpackPSfromUTF8 (BC.pack "hello world") == "hello world"),+ testBool "Checking that hex packing and unpacking preserves 'hello world'"+ (BC.unpack (fromHex2PS $ fromPS2Hex $ BC.pack "hello world")+ == "hello world"),+ testProperty "Checking that email can be parsed"+ (\s ->+ unlines ("":s++["", ""]) ==+ BC.unpack (read_email (renderPS+ $ make_email "reponame" [] (Just (text "contents\n"))+ (text $ unlines s) (Just "filename")))),+ testProperty "Checking email header line length" email_header_no_long_lines,+ testProperty "Checking email for illegal characters" email_header_ascii_chars,+ testProperty "Checking for spaces at start of folded email header lines" email_header_lines_start,+ testProperty "Checking that there are no empty lines in email headers" email_header_no_empty_lines,+ testProperty "Checking that B.concat works" prop_concatPS,+ testProperty "Checking that hex conversion works" prop_hex_conversion,+ testProperty "Checking that show and read work right" prop_read_show,+ testStringList "Checking known commutes" commute_tests,+ testStringList "Checking known merges" merge_tests,+ testStringList "Checking known canons" canonization_tests]+ ++ check_subcommutes subcommutes_inverse "patch and inverse both commutex"+ ++ check_subcommutes subcommutes_nontrivial_inverse "nontrivial commutes are correct"+ ++ check_subcommutes subcommutes_failure "inverses fail"+ +++ [testProperty "Checking that commuting by patch and its inverse is ok" prop_commute_inverse,+ --putStr "Checking that conflict resolution is valid... "+ --runQuickCheckTest returnval prop_resolve_conflicts_valid+ testProperty "Checking that a patch followed by its inverse is identity" prop_patch_and_inverse_is_identity,+ -- The following tests are "wrong" with the Conflictor code.+ --putStr "Checking that a simple smart_merge is sufficient... "+ --runQuickCheckTest returnval prop_simple_smart_merge_good_enough+ --putStr "Checking that an elegant merge is sufficient... "+ --runQuickCheckTest returnval prop_elegant_merge_good_enough+ testProperty "Checking that commutes are equivalent" prop_commute_equivalency,+ testProperty "Checking that merges are valid" prop_merge_valid,+ testProperty "Checking inverses being valid" prop_inverse_valid,+ testProperty "Checking other inverse being valid" prop_other_inverse_valid,+ testStringList "Checking merge swaps" merge_swap_tests,+ -- The patch generator isn't smart enough to generate correct test+ -- cases for the following: (which will be obsoleted soon, anyhow)+ --putStr "Checking the order dependence of unravel... "+ --runQuickCheckTest returnval prop_unravel_order_independent+ --putStr "Checking the unravelling of three merges... "+ --runQuickCheckTest returnval prop_unravel_three_merge+ --putStr "Checking the unravelling of a merge of a sequence... "+ --runQuickCheckTest returnval prop_unravel_seq_merge+ testProperty "Checking inverse of inverse" prop_inverse_composition,+ testProperty "Checking the order of commutes" prop_commute_either_order,+ testProperty "Checking commutex either way" prop_commute_either_way,+ testProperty "Checking the double commutex" prop_commute_twice,+ testProperty "Checking that merges commutex and are well behaved" prop_merge_is_commutable_and_correct,+ testProperty "Checking that merges can be swapped" prop_merge_is_swapable,+ testProperty "Checking again that merges can be swapped (I'm paranoid) " prop_merge_is_swapable,+ testStringList "Checking that the patch validation works" test_check,+ testStringList "Checking commutex/recommute" commute_recommute_tests,+ testStringList "Checking merge properties" generic_merge_tests,+ testStringList "Testing the lcs code" show_lcs_tests,+ testStringList "Checking primitive patch IO functions" primitive_show_read_tests,+ testStringList "Checking IO functions" show_read_tests,+ testStringList "Checking primitive commutex/recommute" primitive_commute_recommute_tests+ ]+\end{code}++\chapter{Unit Tester}++The unit tester function is really just a glorified map for functions that+return lists, in which the lists get concatenated (where map would end up+with a list of lists).++\begin{code}+type PatchUnitTest p = p -> [String]+type TwoPatchUnitTest = Patch -> Patch -> [String]++parallel_pair_unit_tester :: TwoPatchUnitTest -> [(Patch:\/:Patch)] -> [String]+parallel_pair_unit_tester _ [] = []+parallel_pair_unit_tester thetest ((p1:\/:p2):ps)+ = (thetest p1 p2)++(parallel_pair_unit_tester thetest ps)++pair_unit_tester :: TwoPatchUnitTest -> [(Patch:<Patch)] -> [String]+pair_unit_tester _ [] = []+pair_unit_tester thetest ((p1:<p2):ps)+ = (thetest p1 p2)++(pair_unit_tester thetest ps)+\end{code}++\chapter{Email format tests}++These tests check whether the emails generated by darcs meet a few criteria.+We check for line length and non-ASCII characters. We apparently do not have to+check for CR-LF newlines because that's handled by sendmail.++\begin{code}++-- Check that formatHeader never creates lines longer than 78 characters+-- (excluding the carriage return and line feed)+email_header_no_long_lines :: String -> String -> Bool+email_header_no_long_lines field value =+ not $ any (>78) $ map B.length $ bs_lines $ formatHeader cleanField value+ where cleanField = clean_field_string field++bs_lines :: B.ByteString -> [B.ByteString]+bs_lines = finalizeFold . B.foldr splitAtLines (B.empty, [])+ where splitAtLines 10 (thisLine, prevLines) = (B.empty, thisLine:prevLines)+ splitAtLines c (thisLine, prevLines) = (B.cons c thisLine, prevLines)+ finalizeFold (lastLine, otherLines) = lastLine : otherLines++-- Check that an email header does not contain non-ASCII characters+-- formatHeader doesn't escape field names, there is no such thing as non-ascii+-- field names afaik+email_header_ascii_chars :: String -> String -> Bool+email_header_ascii_chars field value+ = not (any (>127) (B.unpack (formatHeader cleanField value)))+ where cleanField = clean_field_string field++-- Check that header the second and later lines of a header start with a space+email_header_lines_start :: String -> String -> Bool+email_header_lines_start field value =+ all (\l -> B.null l || B.head l == 32) (tail headerLines)+ where headerLines = bs_lines (formatHeader cleanField value)+ cleanField = clean_field_string field++-- Checks that there are no lines in email headers with only whitespace+email_header_no_empty_lines :: String -> String -> Bool+email_header_no_empty_lines field value =+ all (not . B.null . B.filter (not . (`elem` [10, 32, 9]))) headerLines+ where headerLines = bs_lines (formatHeader cleanField value)+ cleanField = clean_field_string field++clean_field_string :: String -> String+clean_field_string = filter (\c -> isPrint c && c < '\x80' && c /= ':')++\end{code}++\chapter{LCS}++Here are a few quick tests of the shiftBoundaries function.++\begin{code}+show_lcs_tests :: [String]+show_lcs_tests = concatMap check_known_shifts known_shifts+check_known_shifts :: ([Int],[Int],String,String,[Int],[Int])+ -> [String]+check_known_shifts (ca, cb, sa, sb, ca', cb') = runST (+ do ca_arr <- newListArray (0, length ca) $ toBool (0:ca)+ cb_arr <- newListArray (0, length cb) $ toBool (0:cb)+ let p_a = listArray (0, length sa) $ B.empty:(toPS sa)+ p_b = listArray (0, length sb) $ B.empty:(toPS sb)+ shiftBoundaries ca_arr cb_arr p_a 1 1+ shiftBoundaries cb_arr ca_arr p_b 1 1+ ca_res <- fmap (fromBool . tail) $ getElems ca_arr+ cb_res <- fmap (fromBool . tail) $ getElems cb_arr+ return $ if ca_res == ca' && cb_res == cb' then []+ else ["shiftBoundaries failed on "++sa++" and "++sb++" with "+ ++(show (ca,cb))++" expected "++(show (ca', cb'))+ ++" got "++(show (ca_res, cb_res))++"\n"])+ where toPS = map (\c -> if c == ' ' then B.empty else BC.pack [c])+ toBool = map (>0)+ fromBool = map (\b -> if b then 1 else 0)++known_shifts :: [([Int],[Int],String,String,[Int],[Int])]+known_shifts =+ [([0,0,0],[0,1,0,1,0],"aaa","aaaaa",+ [0,0,0],[0,0,0,1,1]),+ ([0,1,0],[0,1,1,0],"cd ","c a ",+ [0,1,0],[0,1,1,0]),+ ([1,0,0,0,0,0,0,0,0],[1,0,0,0,0,0,1,1,1,1,1,0,0,0], "fg{} if{}","dg{} ih{} if{}",+ [1,0,0,0,0,0,0,0,0],[1,0,0,0,0,1,1,1,1,1,0,0,0,0]), -- prefer empty line at end+ ([0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,1,1,1,1,1,0,0,0], "fg{} if{}","fg{} ih{} if{}",+ [0,0,0,0,0,0,0,0,0],[0,0,0,0,0,1,1,1,1,1,0,0,0,0]), -- prefer empty line at end+ ([],[1,1],"","aa",[],[1,1]),+ ([1,1],[],"aa","",[1,1],[])]+++\end{code}++\chapter{Show/Read tests}++This test involves calling ``show'' to print a string describing a patch,+and then using readPatch to read it back in, and making sure the patch we+read in is the same as the original. Useful for making sure that I don't+have any stupid IO bugs.++\begin{code}+show_read_tests :: [String]+show_read_tests = concatMap t_show_read test_patches +++ concatMap t_show_read test_patches_named+primitive_show_read_tests :: [String]+primitive_show_read_tests = concatMap t_show_read primitive_test_patches+t_show_read :: (Eq p, Show p, Patchy p) => PatchUnitTest p+t_show_read p =+ case readPatch $ renderPS $ showPatch p of+ Just (Sealed p',_) -> if p' == p then []+ else ["Failed to read shown: "++(show p)++"\n"]+ Nothing -> ["Failed to read at all: "++(show p)++"\n"]++instance MyEq p => Eq (Named p) where+ (==) = unsafeCompare+\end{code}++\chapter{Canonization tests}++This is a set of known correct canonizations, to make sure that I'm+canonizing as I ought.++\begin{code}+canonization_tests :: [String]+canonization_tests = concatMap check_known_canon known_canons+check_known_canon :: (Patch, Patch) -> [String]+check_known_canon (p1,p2) =+ if (fromPrims $ concatFL $ mapFL_FL canonize $ sort_coalesceFL $ effect p1) == p2+ then []+ else ["Canonization failed:\n"++show p1++"canonized is\n"+ ++show (fromPrims $ concatFL $ mapFL_FL canonize $ sort_coalesceFL $ effect p1 :: Patch)+ ++"which is not\n"++show p2]+known_canons :: [(Patch,Patch)]+known_canons =+ [(quickhunk 1 "abcde" "ab", quickhunk 3 "cde" ""),+ (quickhunk 1 "abcde" "bd", join_patches [quickhunk 1 "a" "",+ quickhunk 2 "c" "",+ quickhunk 3 "e" ""]),+ (join_patches [quickhunk 4 "a" "b",+ quickhunk 1 "c" "d"],+ join_patches [quickhunk 1 "c" "d",+ quickhunk 4 "a" "b"]),+ (join_patches [quickhunk 1 "a" "",+ quickhunk 1 "" "b"],+ quickhunk 1 "a" "b"),+ (join_patches [quickhunk 1 "ab" "c",+ quickhunk 1 "cd" "e"],+ quickhunk 1 "abd" "e"),+ (quickhunk 1 "abcde" "cde", quickhunk 1 "ab" ""),+ (quickhunk 1 "abcde" "acde", quickhunk 2 "b" "")]+quickhunk :: Int -> String -> String -> Patch+quickhunk l o n = fromPrim $ hunk "test" l (map (\c -> BC.pack [c]) o)+ (map (\c -> BC.pack [c]) n)+\end{code}++\chapter{Merge/unmgerge tests}++It should always be true that if two patches can be unmerged, then merging+the resulting patches should give them back again.+\begin{code}+generic_merge_tests :: [String]+generic_merge_tests =+ case take 400 [(p1:\/:p2)|+ i <- [0..(length test_patches)-1],+ p1<-[test_patches!!i],+ p2<-drop i test_patches,+ check_a_patch $ join_patches [invert p2,p1]] of+ merge_pairs -> (parallel_pair_unit_tester t_merge_either_way_valid merge_pairs) +++ (parallel_pair_unit_tester t_merge_swap_merge merge_pairs)+t_merge_either_way_valid :: TwoPatchUnitTest+t_merge_either_way_valid p1 p2 =+ case join_patches [p2, quickmerge (p1:\/: p2)] of+ combo2 ->+ case join_patches [p1, quickmerge (p2:\/: p1)] of+ combo1 ->+ if not $ check_a_patch $ join_patches [combo1]+ then ["oh my combo1 invalid:\n"++show p1++"and...\n"++show p2++show combo1]+ else+ if check_a_patch $ join_patches [invert combo1, combo2]+ then []+ else ["merge both ways invalid:\n"++show p1++"and...\n"++show p2+++ show combo1+++ show combo2]+t_merge_swap_merge :: TwoPatchUnitTest+t_merge_swap_merge p1 p2 =+ if (swapp $ merge (p2:\/: p1)) == merge (p1:\/:p2)+ then []+ else ["Failed to swap merges:\n"++show p1++"and...\n"++show p2+ ++"merged:\n"++show (merge (p1:\/:p2))++"\n"+ ++"merged and swapped:\n"++show (swapp $ merge (p2:\/: p1))++"\n"]+ where swapp (x :/\: y) = y :/\: x++instance Show p => Show (p :/\: p) where+ show (x :/\: y) = show x ++ " :/\\: " ++ show y+instance Eq p => Eq (p :/\: p) where+ (x :/\: y) == (x' :/\: y') = x == x' && y == y'+\end{code}++\chapter{Commute/recommute tests}++Here we test to see if commuting patch A and patch B and then commuting the+result gives us patch A and patch B again. The set of patches (A,B) is+chosen from the set of all pairs of test patches by selecting those which+commutex with one another.++\begin{code}+commute_recommute_tests :: [String]+commute_recommute_tests =+ case take 200 [(p2:<p1)|+ p1<-test_patches,+ p2<-filter (\p->checkseq [p1,p]) test_patches,+ commutex (p2:<p1) /= Nothing] of+ commute_pairs -> pair_unit_tester t_commute_recommute commute_pairs+ where checkseq ps = check_a_patch $ join_patches ps+primitive_commute_recommute_tests :: [String]+primitive_commute_recommute_tests =+ pair_unit_tester t_commute_recommute+ [(p1:<p2)|+ p1<-primitive_test_patches,+ p2<-primitive_test_patches,+ commutex (p1:<p2) /= Nothing,+ check_a_patch $ join_patches [p2,p1]]+t_commute_recommute :: TwoPatchUnitTest+t_commute_recommute p1 p2 =+ if (commutex (p1:<p2) >>= commutex) == Just (p1:<p2)+ then []+ else ["Failed to recommute:\n"++(show p1)++(show p2)+++ "we saw it as:\n"++show (commutex (p1:<p2))+++ "\nAnd recommute was:\n"++show (commutex (p1:<p2) >>= commutex)+ ++ "\n"]+\end{code}++\chapter{Commute tests}++Here we provide a set of known interesting commutes.+\begin{code}+commute_tests :: [String]+commute_tests =+ concatMap check_known_commute known_commutes+++ concatMap check_cant_commute known_cant_commute+check_known_commute :: (Patch:< Patch, Patch:< Patch) -> [String]+check_known_commute (p1:<p2,p2':<p1') =+ case commutex (p1:<p2) of+ Just (p2a:<p1a) ->+ if (p2a:< p1a) == (p2':< p1')+ then []+ else ["Commute gave wrong value!\n"++show p1++"\n"++show p2+ ++"should be\n"++show p2'++"\n"++show p1'+ ++"but is\n"++show p2a++"\n"++show p1a]+ Nothing -> ["Commute failed!\n"++show p1++"\n"++show p2]+ +++ case commutex (p2':<p1') of+ Just (p1a:<p2a) ->+ if (p1a:< p2a) == (p1:< p2)+ then []+ else ["Commute gave wrong value!\n"++show p2a++"\n"++show p1a+ ++"should have been\n"++show p2'++"\n"++show p1']+ Nothing -> ["Commute failed!\n"++show p2'++"\n"++show p1']+known_commutes :: [(Patch:<Patch,Patch:<Patch)]+known_commutes = [+ (testhunk 1 [] ["A"]:<+ testhunk 2 [] ["B"],+ testhunk 3 [] ["B"]:<+ testhunk 1 [] ["A"]),+ (fromPrim (tokreplace "test" "A-Za-z_" "old" "new"):<+ testhunk 2+ ["hello world all that is old is good old_"]+ ["I don't like old things"],+ testhunk 2+ ["hello world all that is new is good old_"]+ ["I don't like new things"]:<+ fromPrim (tokreplace "test" "A-Za-z_" "old" "new")),+ (testhunk 1 ["A"] ["B"]:<+ testhunk 2 ["C"] ["D"],+ testhunk 2 ["C"] ["D"]:<+ testhunk 1 ["A"] ["B"]),+ (fromPrim (rmfile "NwNSO"):<+ (quickmerge (fromPrim (addfile "hello"):\/:fromPrim (addfile "hello"))),+ (quickmerge (fromPrim (addfile "hello"):\/:fromPrim (addfile "hello"))):<+ fromPrim (rmfile "NwNSO")),++ (quickmerge (testhunk 3 ["o"] ["n"]:\/:+ testhunk 3 ["o"] ["v"]):<+ testhunk 1 [] ["a"],+ testhunk 1 [] ["a"]:<+ quickmerge (testhunk 2 ["o"] ["n"]:\/:+ testhunk 2 ["o"] ["v"])),++ (testhunk 1 ["A"] []:<+ testhunk 3 ["B"] [],+ testhunk 2 ["B"] []:<+ testhunk 1 ["A"] []),++ (testhunk 1 ["A"] ["B"]:<+ testhunk 2 ["B"] ["C"],+ testhunk 2 ["B"] ["C"]:<+ testhunk 1 ["A"] ["B"]),++ (testhunk 1 ["A"] ["B"]:<+ testhunk 3 ["B"] ["C"],+ testhunk 3 ["B"] ["C"]:<+ testhunk 1 ["A"] ["B"]),++ (testhunk 1 ["A"] ["B","C"]:<+ testhunk 2 ["B"] ["C","D"],+ testhunk 3 ["B"] ["C","D"]:<+ testhunk 1 ["A"] ["B","C"])]+ where testhunk l o n = fromPrim $ hunk "test" l (map BC.pack o) (map BC.pack n)++check_cant_commute :: (Patch:< Patch) -> [String]+check_cant_commute (p1:<p2) =+ case commutex (p1:<p2) of+ Nothing -> []+ _ -> [show p1 ++ "\n\n" ++ show p2 +++ "\nArgh, these guys shouldn't commutex!\n"]+known_cant_commute :: [(Patch:< Patch)]+known_cant_commute = [+ (testhunk 2 ["o"] ["n"]:<+ testhunk 1 [] ["A"]),+ (testhunk 1 [] ["A"]:<+ testhunk 1 ["o"] ["n"]),+ (quickmerge (testhunk 2 ["o"] ["n"]:\/:+ testhunk 2 ["o"] ["v"]):<+ testhunk 1 [] ["a"]),+ (fromPrim (hunk "test" 1 ([BC.pack "a"]) ([BC.pack "b"])):<+ fromPrim (addfile "test"))]+ where testhunk l o n = fromPrim $ hunk "test" l (map BC.pack o) (map BC.pack n)+\end{code}++\chapter{Merge tests}++Here we provide a set of known interesting merges.+\begin{code}+merge_tests :: [String]+merge_tests =+ concatMap check_known_merge_equiv known_merge_equivs+++ concatMap check_known_merge known_merges+check_known_merge :: (Patch:\/: Patch, Patch:< Patch) -> [String]+check_known_merge (p1:\/:p2,p1':<p2') =+ case merge (p1:\/:p2) of+ _ :/\: p1a ->+ if (p1a:< p2) == (p1':< p2')+ then []+ else ["Merge gave wrong value!\n"++show p1++show p2+ ++"I expected\n"++show p1'++show p2'+ ++"but found instead\n"++show p1a]+known_merges :: [(Patch:\/:Patch,Patch:<Patch)]+known_merges = [+ (testhunk 2 [BC.pack "c"] [BC.pack "d",BC.pack "e"]:\/:+ testhunk 1 [BC.pack "x"] [BC.pack "a",BC.pack "b"],+ testhunk 3 [BC.pack "c"] [BC.pack "d",BC.pack "e"]:<+ testhunk 1 [BC.pack "x"] [BC.pack "a",BC.pack "b"]),+ (testhunk 1 [BC.pack "x"] [BC.pack "a",BC.pack "b"]:\/:+ testhunk 2 [BC.pack "c"] [BC.pack "d",BC.pack "e"],+ testhunk 1 [BC.pack "x"] [BC.pack "a",BC.pack "b"]:<+ testhunk 2 [BC.pack "c"] [BC.pack "d",BC.pack "e"]),+ (testhunk 3 [BC.pack "A"] []:\/:+ testhunk 1 [BC.pack "B"] [],+ testhunk 2 [BC.pack "A"] []:<+ testhunk 1 [BC.pack "B"] []),+ (fromPrim (rmdir "./test/world"):\/:+ fromPrim (hunk "./world" 3 [BC.pack "A"] []),+ fromPrim (rmdir "./test/world"):<+ fromPrim (hunk "./world" 3 [BC.pack "A"] [])),++ (join_patches [quickhunk 1 "a" "bc",+ quickhunk 6 "d" "ef"]:\/:+ join_patches [quickhunk 3 "a" "bc",+ quickhunk 8 "d" "ef"],+ join_patches [quickhunk 1 "a" "bc",+ quickhunk 7 "d" "ef"]:<+ join_patches [quickhunk 3 "a" "bc",+ quickhunk 8 "d" "ef"]),++ (testhunk 1 [BC.pack "A"] [BC.pack "B"]:\/:+ testhunk 2 [BC.pack "B"] [BC.pack "C"],+ testhunk 1 [BC.pack "A"] [BC.pack "B"]:<+ testhunk 2 [BC.pack "B"] [BC.pack "C"]),++ (testhunk 2 [BC.pack "A"] [BC.pack "B",BC.pack "C"]:\/:+ testhunk 1 [BC.pack "B"] [BC.pack "C",BC.pack "D"],+ testhunk 3 [BC.pack "A"] [BC.pack "B",BC.pack "C"]:<+ testhunk 1 [BC.pack "B"] [BC.pack "C",BC.pack "D"])]+ where testhunk l o n = fromPrim $ hunk "test" l o n+check_known_merge_equiv :: (Patch:\/:Patch,Patch) -> [String]+check_known_merge_equiv (p1:\/: p2, pe) =+ case quickmerge (p1:\/:p2) of+ p1' -> if check_a_patch $ join_patches [invert p1, p2, p1', invert pe]+ then []+ else ["Oh no, merger isn't equivalent...\n"++show p1++"\n"++show p2+ ++"in other words\n" ++ show (p1 :\/: p2)+ ++"merges as\n" ++ show (merge $ p1 :\/: p2)+ ++"merges to\n" ++ show (quickmerge $ p1 :\/: p2)+ ++"which is equivalent to\n" ++ show (effect p1')+ ++ "should all work out to\n"+ ++ show pe]+known_merge_equivs :: [(Patch:\/: Patch, Patch)]+known_merge_equivs = [++ -- The following tests are going to be failed by the+ -- Conflictor code as a cleanup.++ --(addfile "test":\/:+ -- adddir "test",+ -- join_patches [adddir "test",+ -- addfile "test-conflict"]),+ --(move "silly" "test":\/:+ -- adddir "test",+ -- join_patches [adddir "test",+ -- move "silly" "test-conflict"]),+ --(addfile "test":\/:+ -- move "old" "test",+ -- join_patches [addfile "test",+ -- move "old" "test-conflict"]),+ --(move "a" "test":\/:+ -- move "old" "test",+ -- join_patches [move "a" "test",+ -- move "old" "test-conflict"]),+ (fromPrim (hunk "test" 1 [] [BC.pack "A"]):\/:+ fromPrim (hunk "test" 1 [] [BC.pack "B"]),+ fromPrim (hunk "test" 1 [] [BC.pack "A", BC.pack "B"])),+ (fromPrim (hunk "test" 1 [] [BC.pack "a"]):\/:+ fromPrim (hunk "test" 1 [BC.pack "b"] []),+ identity),+ --hunk "test" 1 [] [BC.pack "v v v v v v v",+ -- BC.pack "*************",+ -- BC.pack "a",+ -- BC.pack "b",+ -- BC.pack "^ ^ ^ ^ ^ ^ ^"]),+ (quickhunk 4 "a" "":\/:+ quickhunk 3 "a" "",+ quickhunk 3 "aa" ""),+ (join_patches [quickhunk 1 "a" "bc",+ quickhunk 6 "d" "ef"]:\/:+ join_patches [quickhunk 3 "a" "bc",+ quickhunk 8 "d" "ef"],+ join_patches [quickhunk 3 "a" "bc",+ quickhunk 8 "d" "ef",+ quickhunk 1 "a" "bc",+ quickhunk 7 "d" "ef"]),+ (quickmerge (quickhunk 2 "" "bd":\/:quickhunk 2 "" "a"):\/:+ quickmerge (quickhunk 2 "" "c":\/:quickhunk 2 "" "a"),+ quickhunk 2 "" "abdc")+ ]+\end{code}++It also is useful to verify that it doesn't matter which order we specify+the patches when we merge.++\begin{code}+merge_swap_tests :: [String]+merge_swap_tests =+ concat+ [check_merge_swap p1 p2 |+ p1<-primitive_test_patches,+ p2<-primitive_test_patches,+ check_a_patch $ join_patches [invert p1,p2]+ ]+check_merge_swap :: Patch -> Patch -> [String]+check_merge_swap p1 p2 =+ case merge (p2:\/:p1) of+ _ :/\: p2' ->+ case merge (p1:\/:p2) of+ _ :/\: p1' ->+ case commutex (p2':<p1) of+ Just (p1'b:<_) ->+ if p1'b /= p1'+ then ["Merge swapping problem with...\np1 "+++ show p1++"merged with\np2 "+++ show p2++"p1' is\np1' "+++ show p1'++"p1'b is\np1'b "+++ show p1'b+ ]+ else []+ Nothing -> ["Merge commuting problem with...\np1 "+++ show p1++"merged with\np2 "+++ show p2++"gives\np2' "+++ show p2'++"which doesn't commutex with p1.\n"+ ]+\end{code}++\chapter{Patch test data}++This is where we define the set of patches which we run our tests on. This+should be kept up to date with as many interesting permutations of patch+types as possible.++\begin{code}+test_patches :: [Patch]+test_patches_named :: [Named Patch]+test_patches_addfile :: [Patch]+test_patches_rmfile :: [Patch]+test_patches_hunk :: [Patch]+primitive_test_patches :: [Patch]+test_patches_binary :: [Patch]+test_patches_composite_nocom :: [Patch]+test_patches_composite :: [Patch]+test_patches_two_composite_hunks :: [Patch]+test_patches_composite_hunks :: [Patch]+test_patches_composite_four_hunks :: [Patch]+test_patches_merged :: [Patch]+valid_patches :: [Patch]++test_patches_named = [unsafePerformIO $+ namepatch "date is" "patch name" "David Roundy" []+ (fromPrim $ addfile "test"),+ unsafePerformIO $+ namepatch "Sat Oct 19 08:31:13 EDT 2002"+ "This is another patch" "David Roundy"+ ["This log file has","two lines in it"]+ (fromPrim $ rmfile "test")]+test_patches_addfile = map fromPrim+ [addfile "test",adddir "test",addfile "test/test"]+test_patches_rmfile = map invert test_patches_addfile+test_patches_hunk =+ [fromPrim $ hunk file line old new |+ file <- ["test"],+ line <- [1,2],+ old <- map (map BC.pack) partials,+ new <- map (map BC.pack) partials,+ old /= new+ ]+ where partials = [["A"],["B"],[],["B","B2"]]++primitive_test_patches = test_patches_addfile +++ test_patches_rmfile +++ test_patches_hunk +++ [unsafeUnseal.fst.fromJust.readPatch $+ BC.pack "move ./test/test ./hello",+ unsafeUnseal.fst.fromJust.readPatch $+ BC.pack "move ./test ./hello"] +++ test_patches_binary++test_patches_binary =+ [fromPrim $ binary "./hello"+ (BC.pack $ "agadshhdhdsa75745457574asdgg" +++ "a326424677373735753246463gadshhdhdsaasdgg" +++ "a326424677373735753246463gadshhdhdsaasdgg" +++ "a326424677373735753246463gadshhdhdsaasdgg")+ (BC.pack $ "adafjttkykrehhtrththrthrthre" +++ "a326424677373735753246463gadshhdhdsaasdgg" +++ "a326424677373735753246463gadshhdhdsaasdgg" +++ "a326424677373735753246463gadshhdhdsaagg"),+ fromPrim $ binary "./hello"+ B.empty+ (BC.pack "adafjttkykrere")]++test_patches_composite_nocom =+ take 50 [join_patches [p1,p2]|+ p1<-primitive_test_patches,+ p2<-filter (\p->checkseq [p1,p]) primitive_test_patches,+ commutex (p2:<p1) == Nothing]+ where checkseq ps = check_a_patch $ join_patches ps++test_patches_composite =+ take 100 [join_patches [p1,p2]|+ p1<-primitive_test_patches,+ p2<-filter (\p->checkseq [p1,p]) primitive_test_patches,+ commutex (p2:<p1) /= Nothing,+ commutex (p2:<p1) /= Just (p1:<p2)]+ where checkseq ps = check_a_patch $ join_patches ps++test_patches_two_composite_hunks =+ take 100 [join_patches [p1,p2]|+ p1<-test_patches_hunk,+ p2<-filter (\p->checkseq [p1,p]) test_patches_hunk]+ where checkseq ps = check_a_patch $ join_patches ps++test_patches_composite_hunks =+ take 100 [join_patches [p1,p2,p3]|+ p1<-test_patches_hunk,+ p2<-filter (\p->checkseq [p1,p]) test_patches_hunk,+ p3<-filter (\p->checkseq [p1,p2,p]) test_patches_hunk]+ where checkseq ps = check_a_patch $ join_patches ps++test_patches_composite_four_hunks =+ take 100 [join_patches [p1,p2,p3,p4]|+ p1<-test_patches_hunk,+ p2<-filter (\p->checkseq [p1,p]) test_patches_hunk,+ p3<-filter (\p->checkseq [p1,p2,p]) test_patches_hunk,+ p4<-filter (\p->checkseq [p1,p2,p3,p]) test_patches_hunk]+ where checkseq ps = check_a_patch $ join_patches ps++test_patches_merged =+ take 200+ [joinPatches $ flattenFL p2+>+flattenFL (quickmerge (p1:\/:p2)) |+ p1<-take 10 (drop 15 test_patches_composite_hunks)++primitive_test_patches+ ++take 10 (drop 15 test_patches_two_composite_hunks)+ ++ take 2 (drop 4 test_patches_composite_four_hunks),+ p2<-take 10 test_patches_composite_hunks++primitive_test_patches+ ++take 10 test_patches_two_composite_hunks+ ++take 2 test_patches_composite_four_hunks,+ check_a_patch $ join_patches [invert p1, p2],+ commutex (p1:<p2) /= Just (p2:<p1)+ ]++test_patches = primitive_test_patches +++ test_patches_composite +++ test_patches_composite_nocom +++ test_patches_merged+\end{code}++\chapter{Check patch test}+Check patch is supposed to verify that a patch is valid.++\begin{code}+valid_patches = [(join_patches [quickhunk 4 "a" "b",+ quickhunk 1 "c" "d"]),+ (join_patches [quickhunk 1 "a" "bc",+ quickhunk 1 "b" "d"]),+ (join_patches [quickhunk 1 "a" "b",+ quickhunk 1 "b" "d"])]++test_patches++test_check :: [String]+test_check = concatMap t_test_check valid_patches+t_test_check :: PatchUnitTest Patch+t_test_check p = if check_a_patch p+ then []+ else ["Failed the check: "++show p++"\n"]++prop_hex_conversion :: String -> Bool+prop_hex_conversion s =+ fromHex2PS (fromPS2Hex $ BC.pack s) == BC.pack s+prop_concatPS :: [String] -> Bool+prop_concatPS ss = concat ss == BC.unpack (B.concat $ map BC.pack ss)++-- | Groups a set of tests by giving them the same prefix in their description.+-- When this is called as @check_subcommutes subcoms expl@, the prefix for a+-- test becomes @"Checking " ++ expl ++ " for subcommute "@.+check_subcommutes :: Testable a => [(String, a)] -> String+ -> [Test]+check_subcommutes subcoms expl = map check_subcommute subcoms+ where check_subcommute (name, test) =+ let testName = "Checking" ++ expl ++ " for subcommute " ++ name+ in testProperty testName test+\end{code}++\end{document}++
@@ -0,0 +1,21 @@+{-# LANGUAGE ForeignFunctionInterface #-}++module CtrlC (withCtrlCHandler) where++import Data.Word ( Word32 )+import Foreign.Ptr ( FunPtr )+import Control.Exception ( bracket )++type Handler = Word32 -> IO Int++foreign import ccall "wrapper" wrap :: Handler -> IO (FunPtr Handler)+foreign import stdcall "SetConsoleCtrlHandler" setConsoleCtrlHandler :: FunPtr Handler -> Int -> IO ()+++withCtrlCHandler :: IO () -> IO a -> IO a+withCtrlCHandler handler m = do+ fp <- wrap (\_ctrlType -> handler >> return 1)+ bracket (setConsoleCtrlHandler fp 1) (const $ setConsoleCtrlHandler fp 0) (const m)+++
@@ -0,0 +1,43 @@+{-# OPTIONS_GHC -fglasgow-exts #-}+{-# LANGUAGE ForeignFunctionInterface #-}++module System.Posix where++import Foreign.Ptr ( Ptr, castPtr, plusPtr )+import Foreign.Storable ( peek, poke, sizeOf )+import Foreign.C.Types ( CInt, CUInt, CULong, CTime )+import Foreign.C.String ( CString, withCString )+import Foreign.Marshal.Alloc ( allocaBytes )++import System.Posix.Types ( EpochTime )+import System.IO ( Handle )+++foreign import ccall "sys/utime.h _utime" c_utime :: CString -> Ptr a -> IO CInt++setFileTimes :: FilePath -> EpochTime -> EpochTime -> IO ()+setFileTimes path atime mtime = path `withCString` \s -> do+ allocaBytes 8 $ \p -> do+ poke (castPtr p :: Ptr CTime) (atime)+ poke (castPtr (plusPtr p 4) :: Ptr CTime) (mtime)+ c_utime s p+ return ()+++foreign import ccall "time" c_ctime :: Ptr CTime -> IO CInt++epochTime :: IO EpochTime+epochTime = do+ allocaBytes (sizeOf (undefined :: CTime)) $ \p -> do+ c_ctime p+ t <- peek p :: IO CTime+ return t++foreign import stdcall "winbase.h SleepEx" c_SleepEx :: CULong -> CUInt -> IO CInt++sleep :: Integer -> IO CInt+sleep n = c_SleepEx (1000 * fromIntegral n) 1++handleToFd :: Handle -> IO Int+handleToFd _ = fail "handleToFd not supported!"+
@@ -0,0 +1,97 @@+{-# OPTIONS_GHC -cpp #-}+module System.Posix.Files where++import Foreign.Marshal.Alloc ( allocaBytes )+import Foreign.C.Error ( throwErrnoIfMinus1Retry, throwErrnoPathIf_ )+import Foreign.C.String ( withCString, withCWString, CWString )+import Foreign.C.Types ( CTime, CInt )+import Foreign.Ptr ( Ptr, nullPtr )++import System.Posix.Internals+ ( FDType, CStat, c_fstat, lstat, + sizeof_stat, statGetType, + st_mode, st_size, st_mtime,+ s_isreg, s_isdir, s_isfifo, )+import System.Posix.Types ( Fd(..), CMode, EpochTime, FileMode )++import Data.Bits ( (.|.) )++#if mingw32_HOST_OS+import Data.Int ( Int64 )+#else+import System.Posix.Types ( FileOffset )+#endif++##if mingw32_HOST_OS+type FileOffset = Int64+##endif++data FileStatus = FileStatus {+ fst_type :: FDType,+ fst_mode :: CMode,+ fst_mtime :: CTime,+ fst_size :: FileOffset+ }++getFdStatus :: Fd -> IO FileStatus+getFdStatus (Fd fd) = do+ do_stat (c_fstat fd)++do_stat :: (Ptr CStat -> IO CInt) -> IO FileStatus+do_stat stat_func = do+ allocaBytes sizeof_stat $ \p -> do+ throwErrnoIfMinus1Retry "do_stat" $+ stat_func p+ tp <- statGetType p+ mode <- st_mode p+ mtime <- st_mtime p+ size <- st_size p+ return (FileStatus tp mode mtime (fromIntegral size))++isNamedPipe :: FileStatus -> Bool+isNamedPipe = s_isfifo . fst_mode++isDirectory :: FileStatus -> Bool+isDirectory = s_isdir . fst_mode++isRegularFile :: FileStatus -> Bool+isRegularFile = s_isreg . fst_mode++isSymbolicLink :: FileStatus -> Bool+isSymbolicLink = const False++linkCount :: FileStatus -> Int+linkCount _ = 1++modificationTime :: FileStatus -> EpochTime+modificationTime = fst_mtime++fileSize :: FileStatus -> FileOffset+fileSize = fst_size++fileMode :: () -> ()+fileMode _ = ()++getFileStatus :: FilePath -> IO ()+getFileStatus _ = return ()++setFileMode :: FilePath -> () -> IO ()+setFileMode _ _ = return ()++#include <sys/stat.h>+stdFileMode :: FileMode+stdFileMode = (#const S_IRUSR) .|. (#const S_IWUSR)++++getSymbolicLinkStatus :: FilePath -> IO FileStatus+getSymbolicLinkStatus fp = + do_stat (\p -> (fp `withCString` (`lstat` p)))++#define _WIN32_WINNT 0x0500+foreign import stdcall "winbase.h CreateHardLinkW" c_CreateHardLink :: CWString -> CWString -> Ptr a -> IO CInt++createLink :: FilePath -> FilePath -> IO ()+createLink old new = withCWString old $ \c_old -> withCWString new $ \c_new ->+ throwErrnoPathIf_ (==0) "createLink" new $+ c_CreateHardLink c_new c_old nullPtr
@@ -0,0 +1,69 @@+module System.Posix.IO where++import Foreign.C.String ( withCString )+import Foreign.C.Error ( throwErrnoIfMinus1, throwErrnoIfMinus1_ )++import GHC.Handle ( fdToHandle )++import System.Posix.Internals ( c_open, c_close, c_dup2 )+import System.Posix.Types ( Fd(..), FileMode )+import System.IO ( Handle )++import Data.Bits ( (.|.) )+++stdOutput :: Fd+stdOutput = Fd 1++stdError :: Fd+stdError = Fd 2++data OpenFileFlags = + OpenFileFlags {+ append :: Bool,+ exclusive :: Bool,+ noctty :: Bool,+ nonBlock :: Bool,+ trunc :: Bool+ }+++-- Adapted from System.Posix.IO in ghc+#include <fcntl.h>++openFd :: FilePath -> OpenMode -> Maybe FileMode -> OpenFileFlags -> IO Fd+openFd name how maybe_mode off = do+ withCString name $ \s -> do+ fd <- throwErrnoIfMinus1 "openFd" (c_open s all_flags mode_w)+ return (Fd fd)+ where+ all_flags = binary .|. creat .|. flags .|. open_mode+ flags =+ (if append off then (#const O_APPEND) else 0) .|.+ (if exclusive off then (#const O_EXCL) else 0) .|.+ (if trunc off then (#const O_TRUNC) else 0)+ binary = (#const O_BINARY)+ (creat, mode_w) = maybe (0,0) (\x->((#const O_CREAT),x)) maybe_mode+ open_mode = case how of+ ReadOnly -> (#const O_RDONLY)+ WriteOnly -> (#const O_WRONLY)+ ReadWrite -> (#const O_RDWR)++closeFd :: Fd -> IO ()+closeFd (Fd fd) = throwErrnoIfMinus1_ "closeFd" (c_close fd)+++fdToHandle :: Fd -> IO Handle+fdToHandle fd = GHC.Handle.fdToHandle (fromIntegral fd)++dupTo :: Fd -> Fd -> IO Fd+dupTo (Fd fd1) (Fd fd2) = do+ r <- throwErrnoIfMinus1 "dupTo" (c_dup2 fd1 fd2)+ return (Fd r)++data OpenMode = ReadOnly | WriteOnly | ReadWrite++defaultFileFlags :: OpenFileFlags+defaultFileFlags = OpenFileFlags False False False False False++
@@ -0,0 +1,211 @@+++#include <windows.h>+#include <mapi.h>+#include <stdio.h>++#include "send_email.h"++typedef struct sMapiFuns {+ LPMAPILOGON logon;+ LPMAPISENDMAIL sendmail;+ LPMAPIRESOLVENAME resolve;+ LPMAPIFREEBUFFER free_buf;+ LPMAPILOGOFF logoff;+ HMODULE dll;+} MapiFuns;+++int load_dll(const char* name, MapiFuns* funs);+void free_dll(MapiFuns* funs);++void get_recipient(MapiFuns* funs, const char *name, ULONG recipClass, MapiRecipDesc *desc, lpMapiRecipDesc *desc_lookup);++int send_email(const char *sendname, + const char *recvname, + const char *ccname, + const char *subj,+ const char *body,+ const char *path)+{+ FLAGS flags;+ MapiMessage msg;+ ULONG send_res;+ MapiRecipDesc orig;+ MapiRecipDesc recips[2];+ MapiRecipDesc *orig_lookup, *recip_lookup, *cc_lookup;+ int num_recip = 1, return_code = -1;+ MapiFileDesc attachment;+ MapiFileTagExt file_type;+ const char *filename;+ char *attachment_path = 0;+ MapiFuns funs;+ + if(load_dll("mapistub.dll", &funs) || load_dll("mapi32.dll", &funs)) {+ return_code=0;+ } else {+ fprintf(stderr, "Unable to load mapistub.dll or mapi32.dll: Bailing out. \n");+ return_code=-1;+ }++ if(return_code==0) {+ LHANDLE session;+ /* logon seems to be necessary for outlook express, sometimes,+ and doesn't seem to hurt, otherwise.+ */+ funs.logon(0, 0, 0, MAPI_LOGON_UI, 0, &session);++ + orig_lookup = recip_lookup = cc_lookup = NULL;+ + get_recipient(&funs, sendname, MAPI_ORIG, &orig, &orig_lookup);+ get_recipient(&funs, recvname, MAPI_TO, &recips[0], &recip_lookup);++ if (ccname && strlen(ccname) > 0) {+ get_recipient(&funs, ccname, MAPI_CC, &recips[1], &cc_lookup);+ num_recip++;+ }++ memset(&msg, 0, sizeof(msg));+ msg.lpOriginator = &orig;+ msg.lpRecips = recips;+ msg.lpszMessageType = "text/plain";+ msg.lpszNoteText = (LPSTR) body;+ msg.lpszSubject = (LPSTR)subj;+ msg.nRecipCount = num_recip;+ msg.flFlags = 0;++ if (path) {+ attachment_path = strdup(path);+ /* convert / to \ (thunderbird doesn't like /) */+ char *p = attachment_path;+ while ((p = strchr(p, '/')))+ *p = '\\';++ /* find filename */+ filename = strrchr(attachment_path, '\\');+ if (filename == 0)+ filename = attachment_path;+ else+ filename++;++ memset(&attachment, 0, sizeof(attachment));+ attachment.nPosition = -1;+ attachment.lpszPathName = (LPTSTR)attachment_path;+ attachment.lpszFileName = (LPTSTR)filename;++ attachment.lpFileType = &file_type;+ + memset(&file_type, 0, sizeof(file_type));+ file_type.lpTag = "text/plain";+ file_type.cbTag = sizeof(file_type.lpTag);++ msg.nFileCount = 1;+ msg.lpFiles = &attachment;+ }++ flags = 0;+ send_res = funs.sendmail(0, 0, &msg, flags, 0);++ if (send_res == SUCCESS_SUCCESS)+ return_code = 0;+ else {+ return_code=-1;+ if(send_res==MAPI_E_USER_ABORT) fprintf(stderr, "MAPI error: User aborted.\n");+ else if(send_res== MAPI_E_FAILURE) fprintf(stderr, "MAPI error: Generic error.\n");+ else if(send_res== MAPI_E_LOGIN_FAILURE) fprintf(stderr, "MAPI error: Login failure.\n");+ else if(send_res== MAPI_E_DISK_FULL) fprintf(stderr, "MAPI error: Disk full.\n");+ else if(send_res== MAPI_E_INSUFFICIENT_MEMORY) fprintf(stderr, "MAPI error: Insufficient memory.\n");+ else if(send_res== MAPI_E_ACCESS_DENIED) fprintf(stderr, "MAPI error: Access denied.\n");+ else if(send_res== MAPI_E_TOO_MANY_SESSIONS) fprintf(stderr, "MAPI error: Too many sessions\n");+ else if(send_res== MAPI_E_TOO_MANY_FILES) fprintf(stderr, "MAPI error: Too many files.\n");+ else if(send_res== MAPI_E_TOO_MANY_RECIPIENTS) fprintf(stderr, "MAPI error: Too many recipients.\n");+ else if(send_res== MAPI_E_ATTACHMENT_NOT_FOUND) fprintf(stderr, "MAPI error: Attachment not found.\n");+ else if(send_res== MAPI_E_ATTACHMENT_OPEN_FAILURE) fprintf(stderr, "MAPI error: Failed to open attachment.\n");+ else if(send_res== MAPI_E_ATTACHMENT_WRITE_FAILURE) fprintf(stderr, "MAPI error: Failed to write attachment.\n");+ else if(send_res== MAPI_E_UNKNOWN_RECIPIENT) fprintf(stderr, "MAPI error: Unknown recipient\n");+ else if(send_res== MAPI_E_BAD_RECIPTYPE) fprintf(stderr, "MAPI error: Bad type of recipent.\n");+ else if(send_res== MAPI_E_NO_MESSAGES) fprintf(stderr, "MAPI error: No messages.\n");+ else if(send_res== MAPI_E_INVALID_MESSAGE) fprintf(stderr, "MAPI error: Invalid message.\n");+ else if(send_res== MAPI_E_TEXT_TOO_LARGE) fprintf(stderr, "MAPI error: Text too large.\n");+ else if(send_res== MAPI_E_INVALID_SESSION) fprintf(stderr, "MAPI error: Invalid session.\n");+ else if(send_res== MAPI_E_TYPE_NOT_SUPPORTED) fprintf(stderr, "MAPI error: Type not supported.\n");+ else if(send_res== MAPI_E_AMBIGUOUS_RECIP) fprintf(stderr, "MAPI error: Ambigious recipient.\n");+ else if(send_res== MAPI_E_MESSAGE_IN_USE) fprintf(stderr, "MAPI error: Messag in use.\n");+ else if(send_res== MAPI_E_NETWORK_FAILURE) fprintf(stderr, "MAPI error: Network failure.\n");+ else if(send_res== MAPI_E_INVALID_EDITFIELDS) fprintf(stderr, "MAPI error: Invalid editfields\n");+ else if(send_res== MAPI_E_INVALID_RECIPS) fprintf(stderr, "MAPI error: Invalid recipient(s)\n");+ else if(send_res== MAPI_E_NOT_SUPPORTED) fprintf(stderr, "MAPI error: Operation not supported.\n");+ else fprintf(stderr, "MAPISendMail returned %ld\n", send_res);+ }++ + if (orig_lookup) funs.free_buf(orig_lookup);+ if (recip_lookup) funs.free_buf(recip_lookup);+ if (cc_lookup) funs.free_buf(cc_lookup);+ if (attachment_path) free(attachment_path);++ funs.logoff(session, 0, 0, 0);+ }+ free_dll(&funs);+ return return_code;+}++void get_recipient(MapiFuns* funs, const char *name, ULONG recipClass, MapiRecipDesc *desc, lpMapiRecipDesc *desc_lookup) {+ ULONG ret = funs->resolve(0, 0, (LPSTR) name, 0, 0, desc_lookup);+ if (ret == SUCCESS_SUCCESS) {+ memcpy(desc, *desc_lookup, sizeof(MapiRecipDesc));+ } else {+ /* Default to something sensible if MAPIResolveName is not supported + * by the mail client (thunderbird)+ */+ memset(desc, 0, sizeof(MapiRecipDesc));+ desc->lpszName = (LPSTR)name;+ desc->lpszAddress = (LPSTR)name;+ desc->lpEntryID = 0;+ desc->ulEIDSize = 0;+ }+ desc->ulRecipClass = recipClass;+}+++int load_dll(const char* name, MapiFuns* funs) {+ funs->dll = 0;+ funs->dll = LoadLibrary(name);+ if(funs->dll!=NULL) {+ /* We try first loading by easy name, then by ordinal, and then by other names seen */+ funs->logon = (LPMAPILOGON) GetProcAddress(funs->dll, "MAPILogon");+ if(funs->logon==NULL)+ funs->logon = (LPMAPILOGON) GetProcAddress(funs->dll, (LPCSTR)209);++ funs->logoff = (LPMAPILOGOFF) GetProcAddress(funs->dll, "MAPILogOff");+ if(funs->logoff==NULL)+ funs->logoff = (LPMAPILOGOFF) GetProcAddress(funs->dll, (LPCSTR)210);++ funs->resolve = (LPMAPIRESOLVENAME) GetProcAddress(funs->dll, "MAPIResolveName");+ if(funs->resolve==NULL)+ funs->resolve = (LPMAPIRESOLVENAME) GetProcAddress(funs->dll, (LPCSTR)219);++ funs->free_buf = (LPMAPIFREEBUFFER) GetProcAddress(funs->dll, "MAPIFreeBuffer");+ if(funs->free_buf==NULL)+ funs->free_buf = (LPMAPIFREEBUFFER) GetProcAddress(funs->dll, (LPCSTR)16);+ if(funs->free_buf==NULL)+ funs->free_buf = (LPMAPIFREEBUFFER) GetProcAddress(funs->dll, "MAPIFreeBuffer@4");++ funs->sendmail = (LPMAPISENDMAIL) GetProcAddress(funs->dll, "MAPISendMail");+ if(funs->sendmail==NULL)+ funs->sendmail = (LPMAPISENDMAIL) GetProcAddress(funs->dll, (LPCSTR)211);+ }+ return + funs->dll!=NULL+ && funs->logon!=NULL+ && funs->logoff!=NULL+ && funs->resolve!=NULL+ && funs->free_buf!=NULL+ && funs->sendmail!=NULL;+}+void free_dll(MapiFuns* funs) {+ if(funs->dll!=NULL) FreeLibrary(funs->dll);+ funs->dll=NULL;+}+
@@ -0,0 +1,9 @@++int send_email(const char *sendname, + const char *recvname, + const char *ccname, + const char *subj,+ const char *body,+ const char *path);++
@@ -0,0 +1,7 @@++#include <sys/types.h>++void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);+int munmap(void *start, size_t length);++
@@ -0,0 +1,21 @@+import Darcs.Patch.Real+import Darcs.Patch.Properties+import Darcs.Patch.Bundle+import Darcs.Patch+import Darcs.Repository.ApplyPatches+import Darcs.Patch.Match+import Darcs.Repository.HashedRepo+import Darcs.Resolution+import Darcs.Patch.Check+import Darcs.Repository.Pristine+import Darcs.Repository.DarcsRepo+import Darcs.Repository.Internal+import Darcs.Commands.Unrevert+import Darcs.Commands.WhatsNew+import Darcs.Commands.Show+import Darcs.Commands.Unrecord+import Darcs.Commands.Dist+import Darcs.Commands.TransferMode+import Darcs.Commands.GZCRCs++main = return ()
@@ -0,0 +1,38 @@+#!/usr/bin/env bash+## Test for issueNNNN - <SYNOPSIS: WHAT IS THE BUG? THIS SYNOPSIS+## SHOULD BE ONE OR TWO SENTENCES.>+##+## Copyright (C) YEAR AUTHOR+##+## Permission is hereby granted, free of charge, to any person+## obtaining a copy of this software and associated documentation+## files (the "Software"), to deal in the Software without+## restriction, including without limitation the rights to use, copy,+## modify, merge, publish, distribute, sublicense, and/or sell copies+## of the Software, and to permit persons to whom the Software is+## furnished to do so, subject to the following conditions:+##+## The above copyright notice and this permission notice shall be+## included in all copies or substantial portions of the Software.+##+## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+## EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+## MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND+## NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS+## BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN+## ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN+## CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+## SOFTWARE.++. ../tests/lib # Load some portability helpers.+rm -rf R S # Another script may have left a mess.+darcs init --repo R # Create our test repos.+darcs init --repo S+mkdir R/d/ R/e/ # Change the working tree.+echo 'Example content.' >R/d/f+darcs record --repo R -lam 'Add d/f and e.'+darcs mv --repo R d/f e/+darcs record --repo R -am 'Move d/f to e/f.'+darcs push --repo R S -a # Try to push patches between repos.+darcs push --repo S R+rm -rf R/ S/ # Clean up after ourselves.
@@ -0,0 +1,63 @@+#!/usr/bin/env bash++# Some tests for 'darcs add'++. lib++rm -rf temp1 temp2++# set up the repository+mkdir temp1+cd temp1+darcs init++# Make sure that messages about directories call them directories+mkdir foo.d+mkdir oof.d+darcs add foo.d+darcs add oof.d+not darcs add foo.d 2> log+grep -i directory log+# Try adding the same directory when it's already in the repo+not darcs add foo.d oof.d 2> log+grep -i directories log++# Make sure that messages about files call them files+touch bar+touch baz+darcs add bar+darcs add baz+not darcs add bar 2> log+grep -i 'following file is' log+not darcs add bar baz 2> log+grep -i 'following files are' log++# Make sure that messages about both files and directories say so+not darcs add bar foo.d 2> log+grep -i 'files and directories' log++# Make sure that parent directories are added for files+mkdir -p a.d/aa.d/aaa.d+mkdir -p b.d/bb.d+touch a.d/aa.d/aaa.d/baz+touch a.d/aa.d/aaa.d/bar+darcs add a.d/aa.d/aaa.d/bar a.d/aa.d/aaa.d/baz b.d/bb.d 2> log+test ! -s log # no output++# Make sure that darcs doesn\'t complains about duplicate adds when adding parent dirs.+mkdir c.d+touch c.d/baz+darcs add c.d/baz c.d 2> log+test ! -s log # no output++# Make sure that add output looks good when adding files in subdir+mkdir d.d+touch d.d/foo+darcs add -rv d.d | grep 'd.d/foo'++# 'adding a non-existent dir and file gives the expected message+not darcs add notadir/notafile 2> log+grep -i 'does not exist' log++cd ..+rm -rf temp1
@@ -0,0 +1,19 @@+#!/usr/bin/env bash+set -ev++rm -rf temp1+mkdir temp1+cd temp1+darcs init+touch foo bar+darcs add foo bar++for (( i=0 ; i < 5; i=i+1 )); do+ echo $i >> file-$i;+ darcs add file-$i+done++cd ..++rm -rf temp1+
@@ -0,0 +1,22 @@+#!/usr/bin/env bash++. lib++rm -rf temp1 temp2+mkdir temp1+cd temp1+darcs init+mkdir dir+echo zig > dir/foo+darcs add dir dir/foo+darcs record -a -m add_foo+# Create second repo inside the first+darcs init --repodir=temp2+cd temp2+darcs pull -a ../../temp1+darcs changes -s | grep "A ./dir/foo"+# no differences+diff ../../temp1/dir/foo dir/foo+cd ..++rm -rf temp1
@@ -0,0 +1,33 @@+#!/usr/bin/env bash++set -ev++rm -rf tmp+mkdir tmp+cd tmp+darcs init++empty='test ! -s'+nonempty='test -s'++rm -f foo+darcs add foo >stdout 2>stderr && exit 1 || true+$empty stdout+$nonempty stderr++>foo+darcs add foo >stdout 2>stderr+$empty stdout+$empty stderr++darcs add foo >stdout 2>stderr && exit 1 || true+$empty stdout+$nonempty stderr++rm foo+darcs add foo >stdout 2>stderr && exit 1 || true+$empty stdout+$nonempty stderr++cd ..+rm -rf tmp
@@ -0,0 +1,20 @@+#!/usr/bin/env bash+set -ev++rm -rf temp1 temp2+mkdir temp1 temp2+cd temp1+darcs init+touch foo bar+darcs add foo bar+darcs record -a -m add_foo_bar -A x+darcs mv foo zig+darcs mv bar foo+darcs mv zig bar+darcs record -a -m swap_foo_bar -A x+cd ../temp2+darcs init+darcs pull -v -a ../temp1+cd ..+rm -rf temp1 temp2+
@@ -0,0 +1,18 @@+#!/usr/bin/env bash+set -ev++rm -rf temp1 temp2+mkdir temp1 temp2+cd temp1+darcs init+echo zig > foo+darcs add foo+#sleep 1+darcs record -a -m add_foo -A x+cd ../temp2+darcs init+darcs pull -a ../temp1+cd ..+cmp temp1/foo temp2/foo+rm -rf temp1 temp2+
@@ -0,0 +1,18 @@+#!/usr/bin/env bash+set -ev++rm -rf temp1 temp2+mkdir temp1 temp2+cd temp1+darcs init+touch foo+darcs add foo+darcs record -a -m add_foo -A x+darcs remove foo+darcs record -a -m del_foo -A x+cd ../temp2+darcs init+darcs pull --all ../temp1+cd ..+rm -rf temp1 temp2+
@@ -0,0 +1,19 @@+#!/usr/bin/env bash+set -ev++# This checks for a possible bug in patch selection where the no available+# patches case is hit.++rm -rf temp1+mkdir temp1+cd temp1+darcs init+touch A+darcs add A+darcs record -am A+echo 'l1' >> A+darcs record -am l1+darcs amend-record -a --patch 'A'++cd ..+rm -rf temp1
@@ -0,0 +1,49 @@+#!/usr/bin/env bash++# Testing amend-record.++. lib++rm -rf temp1+# set up the repository+mkdir temp1+cd temp1+darcs init+cd ..++# do some work here+cd temp1+# Plain amend-record+touch foo+darcs add foo+darcs record -a -m add_foo+echo 'another line' > foo+echo y | darcs amend -a foo | grep -i 'amending changes'+darcs changes -v | grep 'another line'+# amend-record of removed file+touch bar1+touch bar2+cat > bar1 << FOO+a line+b line+FOO+darcs add bar1 bar2+darcs record -a -m add_bars+rm -f bar2+echo y | darcs amend -a | grep -i 'finished amending'+# Special case: patch is empty after amend+cp foo foo.old+echo 'another line' >> foo+darcs record -a -m add_line foo | grep -i 'finished recording'+mv foo.old foo+echo y | darcs amend -a foo | grep -i 'amending changes'+# Amend --author, -m, etc+echo "another line" >> foo+echo y | darcs amend -a -m new_name foo | grep -i 'amending changes'+darcs changes --last=1 | grep new_name+echo "another line" >> foo+echo y | darcs amend -a -m new_name -A new_author foo | grep -i 'amending changes'+darcs changes --last=1 | grep new_author+cd ..++rm -rf temp1
@@ -0,0 +1,19 @@+#!/usr/bin/env bash+set -ev++rm -rf temp+mkdir temp+cd temp+darcs init+mkdir a b+touch a/a b/b+darcs add --rec .+darcs record -a -m ab -A test+darcs annotate a/a+# annotate --xml should encode angle brackets in user name+touch c+darcs add c+darcs record -a -m foo -A 'Mark Stosberg <a@b.com>'+darcs annotate --xml c | grep "<a\@b.com>"+cd ..+rm -rf temp
@@ -0,0 +1,63 @@+#!/usr/bin/env bash+set -ev++rm -rf temp0 temp1 temp2++# step 1+mkdir temp0+cd temp0+darcs init --darcs-2+echo m1 > foo+darcs add foo+darcs record -a -m m1 -A moi --ignore-times+cd ..++# step 2+darcs get temp0 temp1+cd temp1+echo a1 > foo+darcs record foo -a -m a1 -A moi --ignore-times+cd ..+++# step 3+cd temp0+echo m2 > foo+darcs record -a -m m2 -A moi --ignore-times+cd ..+++# step 4+cd temp1+darcs pull -a+echo m2-a1 > foo+darcs record -a -m 'Fix conflict m2-a1' -A moi --ignore-times+cd ..++#step 5+cd temp0+echo m3 > foo+darcs record -a -m m3 -A moi --ignore-times+cd ..++#step 6+darcs get temp0 temp2+cd temp2+echo b1 > foo+darcs record -a -m b1 -A moi --ignore-times+cd ..++#step 7+cd temp0+echo m4 > foo+darcs record -a -m m4 -A moi --ignore-times+cd ..++#step 8+cd temp1+darcs pull -a+echo m2-a1-m4 > foo+echo y | darcs mark-conflicts+cd ..++rm -rf temp0 temp1 temp2
@@ -0,0 +1,55 @@+#!/usr/bin/env bash+set -ev++rm -rf temp+mkdir temp+cd temp++darcs init+cat > _darcs/prefs/defaults <<.+ALL author test+ALL ignore-times+ALL ask-deps+.++# add three depending patches for file 'a'+# expect no dependency questions+# 'q' will abort and cause future failure if an unexpected dependency is asked+touch a+darcs add a+echo q | darcs rec -am a0+darcs ann -p a0+echo 1 > a+echo q | darcs rec -am a1+darcs ann -p a1+echo 2 > a+echo q | darcs rec -am a2+darcs ann -p a2++# add some patches for file 'b'+# expect no dependency questions for file 'b',+# but every time expect questions for the three patches of file 'a'+# every 'n' should continue to ask about the next patch+# the first 'y' should make all following dependencies of 'a' implicit and stop asking+# 'q' will abort and cause future failure if an unexpected dependency is asked+touch b+darcs add b+# test 0+echo n/n/n/q | tr / \\012 | darcs rec -am b0+darcs ann -p b0+# test 1+echo 1 > b+echo n/n/y/q | tr / \\012 | darcs rec -am b1+darcs ann -p b1 | grep '^\[a0'+# test 2+echo 2 > b+echo n/y/q | tr / \\012 | darcs rec -am b2+darcs ann -p b2 | grep '^\[a1'+# test 3+echo 3 > b+echo y/q | tr / \\012 | darcs rec -am b3+darcs ann -p b3 | grep '^\[a2'++cd ..+rm -rf temp+
@@ -0,0 +1,34 @@+#!/usr/bin/env bash++. lib++rm -rf temp1 temp2+mkdir temp1+cd temp1+darcs initialize --old-fashioned+echo A > foo+darcs add foo+darcs record -a -m AA -A x+echo '<UGLY HTML-LIKE GARBAGE RETURNED BY BAD HTTP SERVER>' > _darcs/format+cd ..++# ensure that we successfully get repositories even if they have a bogus+# format file, as can happen if no _darcs/format is present (i.e. it's+# generated by an older darcs) and an http server fails to produce a 404+# error code. This is issue757.++darcs get temp1 temp2++echo intentional-error >> temp2/_darcs/format++cat temp2/_darcs/format++rm -rf temp1++not darcs get temp2 temp1 2> err++cat err+grep intentional-error err+grep 'understand repository format' err++rm -rf temp1 temp2
@@ -0,0 +1,38 @@+#!/usr/bin/env bash++# This is a test for issue761, which pointed out that we didn't check the+# syntax of --match patterns until after having spent considerable time+# doing considerable work. So here we construct a very invalid repository,+# and check that darcs fails *before* it notices that it's pulling from a+# bad repository. Thus we verify that we aren't doing any work prior to+# checking the flags.++set -ev++rm -rf temp1 temp2+mkdir temp1+cd temp1+darcs init+touch foo bar+darcs add foo bar+darcs record -a -m 'add two files'+darcs tag -m tag+rm foo bar+darcs record -a -m 'rm two files'+darcs tag -m tag2++rm -rf _darcs/inventories/*+rm -rf _darcs/patches/*++cd ..++mkdir temp2+cd temp2+darcs init+! darcs pull --match 'foobar' ../temp1 2> error+cat error++grep foobar error++rm -rf temp1 temp2+
@@ -0,0 +1,51 @@+#!/usr/bin/env bash++set -ev++rm -fr temp1 temp2++mkdir temp1+cd temp1+darcs init++echo abc > A+darcs add A+echo def > B1+darcs add B1+# darcs record -all --patch-name patch1 # this way it doesn't trigger the bug+for i in 1 2 3 4 5 6 7 8 9 11; do echo y; done | darcs record --patch-name patch1++darcs mv B1 B2+darcs record --all --patch-name patch2+cd ..++mkdir temp2+cd temp2+darcs init+darcs pull --all ../temp1+darcs whatsnew | grep 'No changes'+cd ..++rm -fr temp1 temp2++# issue494: note that in this test, we deliberately select filenames+# with a backwards sorting order+mkdir temp1+cd temp1+darcs init+echo abc > b+darcs add b+darcs record --all -m patch1+darcs mv b a+echo def > a+darcs record --all -m patch2+cd ..++mkdir temp2+cd temp2+darcs init+darcs pull --all ../temp1+darcs whatsnew | grep 'No changes'+cd ..++rm -fr temp1 temp2
@@ -0,0 +1,29 @@+#!/usr/bin/env bash+set -ve+binary=example_binary.png+function checkbinary(){+ cmp $binary ../temp1/$binary+}+rm -rf temp1 temp2+mkdir temp1 temp2+cd temp1+darcs init+cp ../$binary .+darcs add $binary+darcs record -am P1+cd ../temp2+darcs init+test ! -e $binary+darcs pull ../temp1 -a+checkbinary+darcs obliterate -a+test ! -e $binary+darcs pull ../temp1 -a+checkbinary+darcs unrecord -a+checkbinary+darcs revert -a+test ! -e $binary+darcs unrevert -a+checkbinary+rm -rf temp1 temp2
@@ -0,0 +1,71 @@+#!/usr/bin/env bash+set -ev++# Some tests for 'darcs changes'++rm -rf temp1+mkdir temp1+cd temp1+darcs init++date >> date.t+darcs add date.t++darcs record -A 'Mark Stosberg <a@b.com>' -a -m foo date.t++####++darcs changes date.t > out # trivial case first+cat out+grep foo out++darcs changes --last=1 date.t > out+cat out+grep foo out++darcs changes --last 1 --summary date.t > out+cat out+grep foo out++darcs changes --last=1 --xml > out+cat out+grep '<a@b.com>' out # check that --xml encodes < and >++###++# Add 6 records and try again+for i in 0 1 2 3 4 5; do+ date >> date.t+ darcs record -a -m "foo record num $i" date.t+done++darcs changes date.t > out+cat out+grep foo out++darcs changes --last=1 date.t > out+cat out+grep foo out++darcs changes --last 1 --summary date.t > out+cat out+grep foo out++###++darcs changes --context --from-patch='num 1' --to-patch 'num 4' > out+cat out+grep 'num 4' out+grep 'num 3' out+grep 'num 2' out+grep 'num 1' out++###++date >> second_file.t+darcs add second_file.t++darcs record -a -m adding_second_file second_file.t++cd ..+rm -rf temp1
@@ -0,0 +1,18 @@+#!/usr/bin/env bash+set -ev++# RT#544 using context created with 8-bit chars;+rm -rf temp1++mkdir temp1++cd temp1+darcs init+touch foo+darcs record -la -m 'add\212 foo' | grep 'Finished record'+darcs changes --context >context+date > foo+darcs record -a -m 'date foo' | grep 'Finished record'+darcs send -a -o patch --context context . | grep 'Wrote patch to'+cd ..+rm -rf temp1
@@ -0,0 +1,40 @@+#!/usr/bin/env bash++# Some tests for the output of changes when combined with move.++. lib++rm -rf temp1++mkdir temp1+cd temp1+darcs init+date > foo+darcs add foo+darcs record -m 'add foo' -a+mkdir d+darcs add d+darcs record -m 'add d' -a+darcs mv foo d+darcs record -m 'mv foo to d' -a+darcs mv d directory+darcs record -m 'mv d to directory' -a+echo 'How beauteous mankind is' > directory/foo+darcs record -m 'modify directory/foo' -a+darcs changes directory/foo > log+grep 'add foo' log+grep 'mv foo to d' log+echo 'O brave new world' > directory/foo+# darcs should also take unrecorded moves into account+darcs mv directory/foo directory/bar+darcs changes directory/foo > log+grep 'mv foo to d' log+echo 'That has such people in it' > directory/foo+darcs add directory/foo+darcs record -m 'mv foo then add new foo' -a+darcs annotate directory/bar > log+grep 'How beauteous mankind is' log+grep 'O brave new world' log+cd ..++rm -rf temp1
@@ -0,0 +1,35 @@+#!/usr/bin/env bash+## Ensure that "darcs check --test" succeeds when run in a complete+## repository with a dummy test that always succeeds.+##+## Copyright (C) 2008 David Roundy+##+## Permission is hereby granted, free of charge, to any person+## obtaining a copy of this software and associated documentation+## files (the "Software"), to deal in the Software without+## restriction, including without limitation the rights to use, copy,+## modify, merge, publish, distribute, sublicense, and/or sell copies+## of the Software, and to permit persons to whom the Software is+## furnished to do so, subject to the following conditions:+##+## The above copyright notice and this permission notice shall be+## included in all copies or substantial portions of the Software.+##+## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+## EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+## MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND+## NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS+## BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN+## ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN+## CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+## SOFTWARE.++. lib # Load some portability helpers.+rm -rf R # Another script may have left a mess.+darcs init --repo R+darcs setpref --repo R test true+darcs record --repo R -am 'true test'+touch R/f+darcs record --repo R -lam 'added foo'+darcs check --repo R --test+rm -rf R # Clean up after ourselves.
@@ -0,0 +1,42 @@+#!/usr/bin/env bash++# A test for unrecording checkpoint tags, inspired by issue517++set -ev++darcs --version++rm -rf temp temp1 temp2+mkdir temp++# This the last commands would blow up if the "--darcs-2" format wasn't used.+darcs init --darcs-2 --repodir temp+touch temp/x+darcs rec -lam xx --repodir temp+darcs tag -m yy --repodir temp+touch temp/z+darcs rec -lam zz --repodir temp+darcs optimize --checkpoint --repodir temp+darcs get --partial temp temp1+echo 'y' | darcs unrec -p TAG --repodir temp1++# Now, the real tests. Do they return sensible results? +darcs get --partial temp1 temp2;+echo 'n' | darcs unrec --repodir temp1 -p 'zz'++rm -rf temp temp1 temp2;+darcs init --darcs-2 --repodir temp+touch temp/x+darcs rec -lam xx --repodir temp+darcs tag -m yy --repodir temp+touch temp/z+darcs rec -lam zz --repodir temp+darcs optimize --checkpoint --repodir temp+darcs get --partial temp temp1+echo 'y' | darcs unrec -p TAG --repodir temp1++# Now, the real tests. Do they return sensible results? +darcs get --partial temp1 temp2;+echo 'n' | darcs unrec --repodir temp1 -p 'zz'++rm -rf temp temp1 temp2;
@@ -0,0 +1,225 @@+#!/usr/bin/env bash++. lib++# Tests for the doppleganger conflict bug.++# For Zooko, with love+# Also, for issue81.++rm -rf temp+mkdir temp+cd temp++# check that dopplegangers conflict in --old-fashioned+rm -rf tmp_dopple tmp_ganger+mkdir tmp_dopple+cd tmp_dopple+darcs init --old-fashioned++darcs show repo | grep darcs-1.0+touch a.txt+darcs add a.txt+darcs record -A base -am 'adding a.txt'+cd ..++darcs get tmp_dopple tmp_ganger++for repo in tmp_dopple tmp_ganger; do+ echo working on $repo+ cd $repo+ echo "text which appears in both places at once" > a.txt+ darcs record -A $repo -am "recording an identical change in $repo"+ cd ..+done++# Now that the conflict has been set up, try pull one patch from the other.+cd tmp_ganger+darcs pull -a ../tmp_dopple > out+cat out+grep 'conflict' out++# check that dopplegangers conflict in --hashed+rm -rf tmp_dopple tmp_ganger+mkdir tmp_dopple+cd tmp_dopple+darcs init --hashed++darcs show repo | grep hashed+touch a.txt+darcs add a.txt+darcs record -A base -am 'adding a.txt'+cd ..++darcs get tmp_dopple tmp_ganger++for repo in tmp_dopple tmp_ganger; do+ echo working on $repo+ cd $repo+ echo "text which appears in both places at once" > a.txt+ darcs record -A $repo -am "recording an identical change in $repo"+ cd ..+done++# Now that the conflict has been set up, try pull one patch from the other.+cd tmp_ganger+darcs pull -a ../tmp_dopple > out+cat out+grep 'conflict' out+cd ..++# check that dopplegangers do not conflict in --darcs-2+rm -rf tmp_dopple tmp_ganger+mkdir tmp_dopple+cd tmp_dopple+darcs init --darcs-2++darcs show repo | grep darcs-2+touch a.txt+darcs add a.txt+darcs record -A base -am 'adding a.txt'+cd ..++darcs get tmp_dopple tmp_ganger++for repo in tmp_dopple tmp_ganger; do+ echo working on $repo+ cd $repo+ echo "text which appears in both places at once" > a.txt+ darcs record -A $repo -am "recording an identical change in $repo"+ cd ..+done++# Now that the conflict has been set up, try pull one patch from the other.+cd tmp_ganger+darcs pull -a ../tmp_dopple > out+cat out+not grep 'conflict' out+cd ..++# Checking that resolution dopplegangers conflict in --old-fashioned-inventory+rm -rf temp0 temp1 temp2 tmp_dopple tmp_ganger+mkdir temp0+cd temp0+darcs init --old-fashioned-inventory+darcs show repo | grep darcs-1.0+cd ..++# Create a conflict+darcs get --old-fashioned-inventory temp0 temp1+cd temp1+darcs show repo+darcs show repo | grep darcs-1.0+echo temp1 > a.txt+darcs add a.txt+darcs record -A base -am 'adding temp1 a.txt'+cd ..+darcs get temp0 temp2+cd temp2+echo temp2 > a.txt+darcs add a.txt+darcs record -A base -am 'adding temp2 a.txt'+cd ..++# Resolve the conflict the same way on both sides+for repo in tmp_dopple tmp_ganger; do+ echo working on $repo+ darcs get temp1 $repo+ cd $repo+ darcs pull -a ../temp2+ echo "text which appears in both places at once" > a.txt+ darcs record -A $repo -am "recording an identical change in $repo"+ cd ..+done++# Now that the conflict has been set up, try pull one patch from the other.+cd tmp_ganger+darcs pull -a ../tmp_dopple > out+cat out+grep conflict out+cd ..++# Checking that resolution dopplegangers conflict in --hashed+rm -rf temp0 temp1 temp2 tmp_dopple tmp_ganger+mkdir temp0+cd temp0+darcs init --hashed+darcs show repo | grep hashed+cd ..++# Create a conflict+darcs get temp0 temp1+cd temp1+echo temp1 > a.txt+darcs add a.txt+darcs record -A base -am 'adding temp1 a.txt'+cd ..+darcs get temp0 temp2+cd temp2+echo temp2 > a.txt+darcs add a.txt+darcs record -A base -am 'adding temp2 a.txt'+cd ..++# Resolve the conflict the same way on both sides+for repo in tmp_dopple tmp_ganger; do+ echo working on $repo+ darcs get temp1 $repo+ cd $repo+ darcs pull -a ../temp2+ echo "text which appears in both places at once" > a.txt+ darcs record -A $repo -am "recording an identical change in $repo"+ cd ..+done++# Now that the conflict has been set up, try pull one patch from the other.+cd tmp_ganger+darcs pull -a ../tmp_dopple > out+cat out+grep conflict out+cd ..+++# Checking that resolution dopplegangers do not conflict in --darcs-2+rm -rf temp0 temp1 temp2 tmp_dopple tmp_ganger+mkdir temp0+cd temp0+darcs init --darcs-2+darcs show repo | grep darcs-2+cd ..++# Create a conflict+darcs get temp0 temp1+cd temp1+darcs show repo | grep darcs-2+echo temp1 > a.txt+darcs add a.txt+darcs record -A base -am 'adding temp1 a.txt'+cd ..+darcs get temp0 temp2+cd temp2+echo temp2 > a.txt+darcs add a.txt+darcs record -A base -am 'adding temp2 a.txt'+cd ..++# Resolve the conflict the same way on both sides+for repo in tmp_dopple tmp_ganger; do+ echo working on $repo+ darcs get temp1 $repo+ cd $repo+ darcs pull -a ../temp2+ echo "text which appears in both places at once" > a.txt+ darcs record -A $repo -am "recording an identical change in $repo"+ cd ..+done++# Now that the conflict has been set up, try pull one patch from the other.+cd tmp_ganger+darcs pull -a ../tmp_dopple > out+cat out+not grep conflict out+cd ..++cd ..+rm -rf temp
@@ -0,0 +1,54 @@+#!/bin/env bash+#+# Test darcs conflict fight scenario.+#+# Set up two repos RA and RB. Create conflict in RB. +# After resolving conflict in RB, pull new patch from RA.+# Repeat, rinse.+#+# Author: Pekka Pessi+#++set -ev++record="record --ignore-time --all --author X"++rm -rf RA RB+mkdir RA++cd RA+echo 0 > file+darcs init+darcs add file+darcs $record -m0 file+cd ..++darcs get RA RB++# Create conflict in RB+cd RB+echo let it b > file+darcs $record -m B+cd ..++for i in 1 2 3 4 5 # 6 7 8 9 10 11 12+do+ echo Create new patch A$i in RA+ cd RA+ echo a$i > file+ darcs $record -m A$i+ cd ..++ echo Pull patch A$i from RA and get a conflict+ cd RB+ time darcs pull ../RA --verbose --all --patch "^A$i\$"+ cd ..++ echo Resolve conflict and start fighting by recording B$i+ cd RB+ echo let it b > file+ darcs $record -m B$i+ cd ..+done++rm -rf RA RB
@@ -0,0 +1,78 @@+#!/usr/bin/env bash++set -ev++rm -rf temp0 temp1 temp2++# step 1+mkdir temp0+cd temp0+darcs init --darcs-2+echo temp0 > _darcs/prefs/author+echo m1 > foo+darcs add foo+darcs record -a -m m1 --ignore-times+cd ..++# step 2+darcs get temp0 temp1+cd temp1+echo temp1 > _darcs/prefs/author+echo a1 > foo+darcs record foo -a -m a1 --ignore-times+cd ..+++# step 3+cd temp0+echo m2 > foo+darcs record -a -m m2 --ignore-times+cd ..+++# step 4+cd temp1+darcs pull -a+echo m2-a1 > foo+darcs record -a -m 'Fix conflict m2-a1' --ignore-times+echo a2 > foo+darcs record -a -m a2 --ignore-times+cd ..++#step 5+cd temp0+echo m3 > foo+darcs record -a -m m3 --ignore-times+cd ..++#step 6+darcs get temp0 temp2+cd temp2+echo temp2 > _darcs/prefs/author+echo b1 > foo+darcs record -a -m b1 --ignore-times++cd ..++#step 7+cd temp0+echo m4 > foo+darcs record -a -m m4 --ignore-times+cd ..++#step 8+cd temp1+darcs pull -a+echo m2-a1-m4 > foo+darcs record -a -m 'Fix three-way m2/m2-a1/m4' --ignore-times+echo a3 > foo+darcs record -a -m a3 --ignore-times+cd ..++#step 9+cd temp1+darcs pull -av ../temp2+cd ..++rm -rf temp0 temp1 temp2+
@@ -0,0 +1,18 @@+#!/usr/bin/env bash+set -ev++rm -rf temp-$$+mkdir temp-$$+cd temp-$$+set -e+darcs initialize+echo text > afile.txt+darcs add afile.txt+darcs record --author me --all --no-test --patch-name init+darcs diff+darcs diff -p . --store > diffinmem+darcs diff -p . > diffondisk+diff diffinmem diffondisk+cd ..++rm -rf temp-$$
@@ -0,0 +1,23 @@+#!/usr/bin/env bash+set -ev++T=temp+rm -rf "$T"+mkdir "$T"+echo "$T"+cd "$T"+set -e+darcs initialize+echo text > afile.txt+darcs add afile.txt+darcs record --author me --all --no-test --patch-name init+mkdir d+darcs add d+mkdir d/e+darcs add d/e+darcs mv afile.txt d/e/afile.txt+echo altered_text > d/e/afile.txt+darcs record --author me --all --no-test --patch-name confusion+test ! -f _darcs/pristine/afile.txt+echo y/d/y | tr / \\012 | darcs unrecord+rm -rf "$T"
@@ -0,0 +1,20 @@+#!/usr/bin/env bash+. lib++# Some tests for 'darcs whatsnew '++rm -rf temp1+mkdir temp1+cd temp1+darcs init+touch look_summary.txt+# --disable works on command line+not darcs whatsnew -sl --disable 2> log+grep -i disable log+# --disable works from defaults+echo 'whatsnew --disable' > _darcs/prefs/defaults+not darcs whatsnew -sl 2> log+grep -i disable log+cd ..++rm -rf temp1
@@ -0,0 +1,15 @@+#!/usr/bin/env bash++. lib++rm -rf temp1+mkdir temp1+cd temp1+darcs init+touch foo+darcs add foo+darcs record -a -m add_foo | grep -i "finished recording"+darcs dist+cd ..++rm -rf temp1
@@ -0,0 +1,29 @@+#!/usr/bin/env bash+set -ev++# This demonstrates a bug that happens if you revert followed by+# a partial unrevert and a full unrevert. It requires that+# the second unrevert is working with patches who's contents need+# to be modified by the commute in the first unrevert.++rm -rf temp1+mkdir temp1+cd temp1+darcs init+echo line1 >> A+echo line2 >> A+echo line3 >> A+echo line4 >> A+echo line5 >> A+echo line6 >> A+darcs add A+darcs record -am A+sed 's/line2/Line2/' A > A1; rm A; mv A1 A+sed '4d' A > A1; rm A; mv A1 A+sed 's/line6/Line6/' A > A1; rm A; mv A1 A+darcs revert -a+echo nyn | darcs unrev+darcs unrev -a++cd ..+rm -rf temp1
binary file changed (absent → 6742 bytes)
@@ -0,0 +1,37 @@+#!/usr/bin/env bash++# Some tests for launching external commands++. lib++rm -rf temp1++touch_fakessh='./touch-fakessh'+if echo $OS | grep -i windows; then+ touch_fakessh="touch_fakessh.bat"+fi+export DARCS_SSH=$touch_fakessh+export DARCS_SCP=$touch_fakessh+export DARCS_SFTP=$touch_fakessh+rm -rf 'fakessh'+rm -rf 'touch-fakessh'++# make our ssh command one word only+echo 'echo hello > fakessh' > $touch_fakessh+chmod u+x $touch_fakessh+# first test the DARCS_SSH environment variable+not darcs get example.com:foo+grep hello fakessh+rm -f fakessh++# now make sure that we don't launch ssh for nothing+mkdir temp1+cd temp1+darcs init+cd ..+darcs get temp1 > log+not grep fakessh log+not darcs get http://example.com/foo+not grep fakessh log+cd ..+rm -rf temp1
@@ -0,0 +1,114 @@+#!/usr/bin/env bash++# Some tests for proper handling of filepaths++. lib++DIR=`pwd`++rm -rf temp1 temp2++# Make sure that init works with --repodir+darcs init --repodir=temp1+test -d temp1/_darcs++# add some meat to that repository+cd temp1+touch baz+darcs add baz+darcs record -m moo -a+cd ..++# ----------------------------------------------------------------------+# local vs remote filepaths+# ----------------------------------------------------------------------++# trick: OS-detection (if needed)+if echo $OS | grep -i windows; then+ echo This test does not work on Windows+else+ darcs get temp1 temp2+ cd temp2+ mkdir -p dir+ darcs add dir+ cd dir+ touch foo:bar+ darcs add --reserved-ok foo:bar+ cd ../..+ rm -rf temp2+fi++# ----------------------------------------------------------------------+# repodir stuff+# ----------------------------------------------------------------------+mkdir -p temp1/non-darcs+# FIXME: This test does not seem to make much sense+# --repodir is not recursive+not darcs get temp1/non-darcs 2> log+grep "Not a repository" log+rm -rf temp1/non-darcs+rm -rf non-darcs++# get accepts --repodir.+darcs get --repodir=temp2 temp1 | grep -i "Finished getting"+test -d temp2/_darcs+rm -rf temp2+# get accepts absolute --repodir.+darcs get --repodir="${DIR}/temp2" temp1 | grep -i "Finished getting"+test -d temp2/_darcs++# changes accepts --repodir.+darcs changes --repodir=temp1 | grep -i "moo"+# changes accepts absolute --repo.+darcs changes --repo="${DIR}/temp1" | grep -i "moo"+# changes accepts relative --repo.+darcs changes --repo=temp1 | grep -i "moo"+# [issue467] context --repodir+darcs changes --context --repodir=temp1 | grep 'Context:'++# dist accepts --repodir.+darcs dist --repodir=temp1 | grep -i "Created dist"++# optimize accepts --repodir.+darcs optimize --reorder-patches --repodir=temp1 | grep -i "done optimizing"++# repair accepts --repodir.+darcs repair --repodir=temp1 | grep -i "already consistent"++# replace accepts --repodir.+darcs replace --repodir=temp1 foo bar++# setpref accepts --repodir.+darcs setpref --repodir=temp1 test echo | grep -i "Changing value of test"++# trackdown accepts --repodir.+darcs trackdown --repodir=temp1 | grep -i "Success!"++# ----------------------------------------------------------------------+# converting between absolute and relative paths+# ----------------------------------------------------------------------++rm -rf temp3+darcs get temp1 temp3+cd temp3+mkdir -p a/b+cd ..++cd temp2+echo hello 1 >> baz+darcs record -m hello1 -a+echo hello 2 >> baz+darcs record -m hello2 -a+cd ..++# can handle .. path+cd temp3+darcs pull ../temp2 -p1 --all | grep -i 'Finished pulling'+darcs pull --dry-run | grep hello2+cd a/b+#[issue268] repodir with subdir+darcs pull --dry-run | grep hello2+cd ..+cd ..++rm -rf log temp1 temp2 temp3
@@ -0,0 +1,18 @@+#!/usr/bin/env bash++. lib++rm -rf temp1+mkdir temp1+cd temp1+darcs init+touch t.t+darcs add t.t+darcs record -am "initial add"+darcs changes --context > my_context+DIR=`pwd`+abs_to_context="${DIR}/my_context"+cd ..+rm -rf temp2+darcs get temp1 --context="${abs_to_context}" temp2+rm -rf temp1 temp2
@@ -0,0 +1,87 @@+#!/usr/bin/env bash+set -ev++rm -rf temp1 temp2+mkdir temp1+cd temp1+darcs initialize+echo ALL ignore-times >> _darcs/prefs/defaults+echo A > foo+darcs add foo+darcs record -a -m AA -A x+echo B > foo+darcs record -a -m BB -A x+echo C > foo+darcs record -a -m CC -A x+darcs tag -m 1.0 -A x+cp foo foo_version_1.0+echo D > foo+darcs record -a -m DD -A x+echo E > foo+darcs record -a -m EE -A x+echo F > foo+darcs record -a -m FF -A x+++# Check that get store commuted patches+cd ..+darcs get --tag 1.0 --repo-name temp2 temp1+cmp temp2/foo temp1/foo_version_1.0+rm -rf temp1 temp2 temp3++mkdir temp1+cd temp1+darcs init+cat > file <<EOF+1+2+3+4+EOF+darcs rec -Ax -alm 'Add file'+cat > file <<EOF+2+3+4+EOF+darcs rec -Ax -alm 'Remove line 1'+cat > file <<EOF+2+4+EOF+darcs rec -Ax -alm 'Remove line 3'+cd ..+mkdir temp2+cd temp2+darcs init+echo y/n/y | tr / \\012 | darcs pull ../temp1+darcs tag -Ax -m Tag+darcs push -a ../temp1+cd ..+darcs get --tag=Tag temp1 temp3+cd temp3+darcs check+cd ..+rm -rf temp1 temp2 temp3++# Check that pending looks ok+mkdir temp1+cd temp1+darcs init+mkdir d+darcs add d+darcs rec -Ax -am 'add d'+darcs tag -Ax t+rmdir d+darcs rec -Ax -am 'rm d'+cd ..+darcs get --tag t temp1 temp2+cd temp2+if [ -f _darcs/patches/pending ]; then+ if grep -v '^[{}]$' _darcs/patches/pending >/dev/null; then+ cat _darcs/patches/pending+ exit 1+ fi+fi+cd ..+rm -rf temp1 temp2
@@ -0,0 +1,39 @@+#!/usr/bin/env bash+## Test for gzcrcs command - check and repair corrupted CRCs on+## compressed files+##+## Copyright (C) 2009 Ganesh Sittampalam+##+## Permission is hereby granted, free of charge, to any person+## obtaining a copy of this software and associated documentation+## files (the "Software"), to deal in the Software without+## restriction, including without limitation the rights to use, copy,+## modify, merge, publish, distribute, sublicense, and/or sell copies+## of the Software, and to permit persons to whom the Software is+## furnished to do so, subject to the following conditions:+##+## The above copyright notice and this permission notice shall be+## included in all copies or substantial portions of the Software.+##+## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+## EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+## MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND+## NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS+## BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN+## ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN+## CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+## SOFTWARE.++# need to do this before loading lib as that sets -e+darcs gzcrcs --help > /dev/null+if [ $? == 2 ] ; then echo gzcrcs not supported by this darcs ; exit 0 ; fi++. ../tests/lib # Load some portability helpers.+rm -rf maybench-crc+gunzip -c repos/maybench-crc.tgz | tar xf -+cd maybench-crc+not darcs gzcrcs --check+darcs gzcrcs --repair+darcs gzcrcs --check+cd ..+rm -rf maybench-crc
@@ -0,0 +1,16 @@+#!/usr/bin/env bash++set -ev++# Print some stuff out for debugging if something goes wrong:+echo $HOME+echo $PATH+which darcs+command -v darcs++# Check things that should be true when all the testscripts run++test -f "$HOME"/harness.sh || { echo "HOME=\"$HOME\" is not the test suite directory"; exit 1; }++homedirname=`dirname "$HOME"`+command -v darcs | grep "$homedirname"
@@ -0,0 +1,194 @@+#!/usr/bin/env bash++set -ev++# We'd just use `diff -x _darcs -r' if -x was portable.+diffx () {+ { find $1 -type f; find $2 -type f; } |+ sed -e '/.*\/_darcs\//d' -e 's;^[^/]*;;' | grep -v darcs.tix | sort | uniq |+ {+ while read part; do+ diff -c $1$part $2$part+ done+ }+}++makepristine () {+ rm -rf pristine+ mkdir pristine+ for i in `darcs show files --no-files --no-pending`; do+ echo mkdir -p pristine/$i;+ mkdir -p pristine/$i;+ done+ for i in `darcs show files --no-directories --no-pending`; do+ echo darcs show contents $i ">" pristine/$i;+ darcs show contents $i > pristine/$i;+ cat pristine/$i;+ done+}++++rm -rf temp1 temp2 temp3 temp4 temp5+mkdir temp1+cd temp1+darcs init --hashed+touch foo+darcs add foo+darcs rec -m t1 -a -A tester+echo 1 >> foo+darcs what -s | grep -v No\ changes+darcs what -l | grep -v No\ changes+darcs what -sl | grep -v No\ changes+makepristine+cd ..++darcs get temp1 temp2+cd temp2+darcs changes+makepristine+cd ..++darcs get --hashed temp1 temp3+cd temp3+darcs changes+cp _darcs/hashed_inventory inv+darcs optimize+diff -c inv _darcs/hashed_inventory+rm inv+makepristine+cd ..+cat temp3/pristine/foo++diffx temp2 temp3+diff -rc temp1/pristine temp3/pristine+diff -rc temp2/pristine temp3/pristine++cd temp1+darcs record -a -A tester -m t2+darcs push ../temp2 -a+darcs push ../temp3 -a+makepristine+cd ..++cd temp3+makepristine+cd ..+cd temp2+makepristine+cd ..++diffx temp2 temp3+diff -rc temp1/pristine temp3/pristine+diff -rc temp2/pristine temp3/pristine++cd temp1+date > foo+darcs record -a -A tester -m t3+makepristine+cd ../temp2+darcs pull -a+makepristine+cd ../temp3+darcs pull -a+darcs check+makepristine+cd ..++diffx temp2 temp3+diff -rc temp1/pristine temp3/pristine+diff -rc temp2/pristine temp3/pristine++cd temp1+darcs put --hashed ../temp4+cd ..+cd temp4+makepristine+cd ..++diffx temp2 temp4+diff -rc temp2/pristine temp4/pristine++cd temp1+darcs tag -A tagger -m atag+darcs check+darcs optimize+darcs check+darcs changes | grep t1+cd ..++cd temp3+date > foobarpatch+darcs add foobarpatch+darcs record -a -A silly -m foobarpatch+darcs check+darcs optimize+darcs check+darcs pull -a ../temp1+darcs check+darcs optimize --reorder-patches+darcs check+grep 'Starting with inventory' _darcs/hashed_inventory+cd ..++cd temp1+darcs pull -a ../temp3+cd ..++diff -c temp1/_darcs/hashed_inventory temp3/_darcs/hashed_inventory++cd temp4+darcs pull -p foobarpatch -a ../temp3+darcs pull -a ../temp1+darcs optimize --reorder+darcs check+darcs push ../temp1+cd ..++diff temp1/_darcs/hashed_inventory temp4/_darcs/hashed_inventory++rm -rf temp3+darcs get --old-fashioned temp1 temp3++rm -rf "$HOME"/.darcs/sources+echo cache:"$HOME"/.darcs/cache > "$HOME"/.darcs/sources+darcs get --hashed temp3 temp5+rm -rf temp5+# check that sources directory was created:+cd "$HOME"/.darcs/sources && cd -++darcs get temp1 temp5 --hashed --old-fashioned+cd temp5+darcs obliterate --last 3 -a+darcs pull ../temp1 -a+darcs obliterate --last 3 -a+darcs pull ../temp2 -a+darcs check+darcs obliterate --last 3 -a+darcs pull ../temp3 -a+darcs obliterate --last 3 -a+darcs pull ../temp4 -a+cd ..++cd temp4+darcs obliterate --last 3 -a+darcs pull ../temp5 -a+cd ..++cd temp3+darcs obliterate --last 3 -a+darcs pull ../temp5 -a+cd ..++cd temp2+darcs obliterate --last 3 -a+darcs pull ../temp5 -a+cd ..++cd temp1+darcs obliterate --last 3 -a+darcs pull ../temp5 -a+cd ..++rm -rf temp1 temp2 temp3 temp4 temp5+
@@ -0,0 +1,59 @@+#!/usr/bin/env bash++RESULT=tmpfile+ROOT=..+ERRORSTATUS=0++# lookfor ( $1=what, $2=reason, $3=source module exception )+lookfor () {+ rm -f "$RESULT"+ darcs query manifest --repodir="$ROOT" | grep '\.l\?hs$' | while read f; do+ grep -Hnwe "$1" "$ROOT/$f" | grep -v "$3\.$1" | \+ grep -v ":[0-9]\+:import " | grep -Fv "ratify $1: " >> "$RESULT"+ done+ if [ -s "$RESULT" ]; then+ echo "Found the following unratified uses of $1:"+ # ugly sed expresion to fix relative paths; think pretty cat+ sed -e 's/[^:]*\/\.\///' "$RESULT"+ echo "$2"+ echo "Comment 'ratify $1: <why>' on the same line to allow it"+ echo+ ERRORSTATUS=1+ fi+ rm -f "$RESULT"+}++# On 2009-02-12 Petr Rockai explained this on darcs-users:+# The problem with Prelude readFile is that it's based on hGetContents, which+# is lazy by definition. This also means that unless you force consumption of+# the produced list, it will keep an fd open for the file, possibly+# indefinitely. This is called a fd leak. Other than being annoying and if done+# often, leading to fd exhaustion and failure to open any new files (which is+# usually fatal), it also prevents the file to be unlinked (deleted) on win32.+#+# On the other hand, *strict* bytestring version of readFile will read the whole+# file into a contiguous buffer, *close the fd* and return. This is perfectly+# safe with regards to fd leaks. Btw., this is *not* the case with lazy+# bytestring variant of readFile, so that one is unsafe.+lookfor readFile \+ "Prelude.readFile doesn't ensure the file is closed before it is deleted!\nConsider import Data.ByteString.Char8 as B (readFile), B.readFile instead." \+ B # importing readFile from Data.ByteString as B, is allowed++lookfor hGetContents \+ "hGetContents doesn't ensure the file is closed before it is deleted!"++# look for tabs in haskell source+rm -f "$RESULT"+darcs query manifest --repodir="$ROOT" | grep '\.l\?hs$' | while read f; do+ grep -FHnwe " " "$ROOT/$f" >> "$RESULT"+done+if [ -s "$RESULT" ]; then+ echo "Found the following lines with unwanted tabs:"+ # ugly sed expresion to fix relative paths; think pretty cat+ sed -e 's/[^:]*\/\.\///' "$RESULT"+ echo+ ERRORSTATUS=1+fi+rm -f "$RESULT"++exit "$ERRORSTATUS"
@@ -0,0 +1,32 @@+#!/bin/sh++set -ev++rm -rf temp1 temp2+mkdir temp1+cd temp1+darcs init --darcs-2+echo first > a+darcs add a+darcs record -am 'first'+cd ..+darcs get temp1 temp2++cd temp1+echo second > a+darcs record -am 'first to second'+echo first > a+darcs record -am 'second back to first'+cd ..++cd temp2+echo third > a+darcs record -am 'first to third'+cd ..++cd temp1+darcs pull -a ../temp2 | grep conflict+grep third a+cd ..++rm -rf temp1 temp2
@@ -0,0 +1,70 @@+#!/usr/bin/env bash+set -ev++# A test for a missed resolution, inspired by bug #10 in RT++rm -rf temp1 temp2+mkdir temp1+cd temp1+darcs init+cd ..+mkdir temp2+cd temp2+darcs init+cd ..++# set up temp1+cd temp1+cat > A << FOO+i++m+b+v+FOO+darcs add A+darcs record -m 'add' --all+cd ..++# set up temp2+cd temp2+darcs pull --all ../temp1+cat > A << FOO+J+i++C2++m+D+b+v+FOO+darcs record -m 'change2' --all+cd ..++# generate a conflict+cd temp1+cat > A << FOO+I+i++C1++m+b++FOO+darcs record -m 'change1' --all+darcs pull --all ../temp2+# we should have a marked conflict now.+grep 'v v' A+# we resolve it simply by removing conflict markers.+sed -e '/\^ \^|\*\*|v v/d' A > temp+mv temp A+darcs record -m resolution --all+# now mark-conflicts shouldn't find any unmarked conflicts+darcs mark-conflicts | grep "No conflicts to mark"+cd ..++rm -rf temp1 temp2
@@ -0,0 +1,38 @@+#!/usr/bin/env bash++. lib+++rm -rf temp1+mkdir temp1+cd temp1+darcs init++touch foo+darcs add foo++cat > log <<EOF+add file foo+Ignore-this: My private secret.+EOF++echo n | not darcs record -a --logfile log > out+cat out+grep Proceed out++darcs whatsnew++echo y | darcs record -a --logfile log++not darcs whatsnew++darcs changes > out+cat out+not grep 'My private secret' out++darcs changes --xml > out+cat out+grep 'My private secret' out++cd ..+rm -rf temp1
@@ -0,0 +1,29 @@+#!/usr/bin/env bash++set -ev++rm -rf temp1++mkdir temp1+cd temp1+darcs init+echo -e 'foo\nbar\nbaz' > f+darcs rec -Ax -alm p1+echo -e 'foo\nbar\nwibble' > f+darcs rec -Ax -alm p2+sleep 1 # ensure the timestamps would differ after this change alone+echo -e 'baz\nbar\nwibble' > f++# check that wh (without --ignore-times) sees the change now+darcs wh > whatsnew+grep 'foo' whatsnew++# the problematic unpull+darcs unpull --last 1 -a --ignore-times++# whatsnew will now think there are no changes without --ignore-times+darcs wh > whatsnew+grep 'foo' whatsnew++cd ..+rm -rf temp1
@@ -0,0 +1,21 @@+#!/usr/bin/env bash+set -ev++rm -rf temp+mkdir temp+cd temp++darcs initialize+echo text > afile.txt+darcs add afile.txt+darcs record --author me --all --no-test --patch-name init+mkdir d+echo The following mv should fail, since d isnt in the repo.+if darcs mv afile.txt d/afile.txt; then+false+fi++# Now clean up.+cd ..+rm -rf temp+
@@ -0,0 +1,33 @@+#!/usr/bin/env bash+set -ev++rm -rf temp+mkdir temp+cd temp+darcs init+echo ALL ignore-times > _darcs/prefs/defaults+echo a > foo+darcs add foo+darcs record -a -m aa -A test+echo b > foo+echo y | darcs revert -a+echo y/d/y | tr / \\012 | darcs unrecord+# since the unrevert is impossible, we should fail if it succeeds...+echo y | darcs unrevert && exit 1 || true++# now let's try a possible unrevert, just for fun...+echo b >> foo+darcs record -a -m bb -A test+echo f/b | tr / \\012 > foo+darcs record -a -m 'aaa becomes f' -A test+date >> foo+echo y | darcs revert -a+echo y/d/y | tr / \\012 | darcs unpull+# Now add the date back on at the end:+echo y | darcs unrevert+echo 'M ./foo +1' > correct_summary+darcs whatsnew --dont-look-for-adds --summary > actual_summary+diff -c correct_summary actual_summary++cd ..+rm -rf temp
@@ -0,0 +1,19 @@+#!/usr/bin/env bash++. lib++rm -rf temp1 temp2++mkdir temp1+cd temp1+darcs init+test -d _darcs+not darcs init+cd ..++# Some tests for the repodir flag+mkdir temp2+darcs init --repodir temp2+test -d temp2/_darcs++rm -rf temp1 temp2
@@ -0,0 +1,22 @@+#!/usr/bin/env bash++# A regression test for issue567++set -ev++rm -rf temp+mkdir temp+cd temp+darcs init+mkdir dir+touch dir/t.t+darcs add dir+darcs add dir/t.t+darcs record -am 'initial add'++# grand finale? Can we move the file to itself? +darcs mv dir/t.t dir/++cd ..++rm -rf temp
@@ -0,0 +1,18 @@+#!/usr/bin/env bash+set -ev+rm -rf temp1+mkdir temp1+cd temp1+darcs --version+darcs init+echo temp1 >File.hs+darcs add File.hs+darcs record File.hs -a -m "add File"+rm File.hs+darcs record -a -m "rm File"+darcs cha+darcs unrecord -p "rm File" -a+darcs cha+darcs record -a -m "re-rm File"+cd ..+rm -rf temp1
@@ -0,0 +1,13 @@+#!/usr/bin/env bash+set -ev++rm -rf temp1+mkdir temp1+cd temp1+darcs init+printf "File contents" > foo+darcs add foo+darcs record -a -m 'foo' foo+for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16; do cat foo foo > foo2; mv foo2 foo; done+darcs what+cd ..
@@ -0,0 +1,68 @@+#!/usr/bin/env bash+++. lib++# pull from not empty repo to empty repo+rm -rf temp1 temp2+mkdir temp1 temp2++cd temp1+darcs init+echo a > a+darcs add a+darcs record --all --patch-name=a+cd ../temp2+darcs init+darcs pull --all --dont-allow-conflicts ../temp1+test `darcs changes --count` = "1"+cd ..++# push from not empty repo to empty repo+rm -rf temp1 temp2+mkdir temp1 temp2++cd temp1+darcs init+cd ../temp2+darcs init+echo a > a+darcs add a+darcs record --all --patch-name=a+darcs push --all ../temp1+cd ../temp1+test `darcs changes --count` = "1"+cd ..++# send from not empty repo to not empty repo+rm -rf temp1 temp2+mkdir temp1 temp2++cd temp1+darcs init+echo a > a+darcs add a+darcs record --all --patch-name=a++for i in 1 2 3 4 5 6 7 8 9; do+ echo Change number $i >> a+ darcs record -a -m "change a $i"+done++cd ../temp2+darcs init+echo b > b+darcs add b+darcs record --all --patch-name=b++for i in 1 2 3 4 5 6 7 8 9; do+ echo Change number $i >> b+ darcs record -a -m "change b $i"+done++echo no | darcs send --all --to=random@random --sendmail-command=false ../temp1+echo yes | not darcs send --all --to=random@random --sendmail-command=false ../temp1+not darcs send --all --to=random@random --sendmail-command=false --allow-unrelated-repos ../temp1+cd ..++rm -rf temp1 temp2
@@ -0,0 +1,13 @@+#!/usr/bin/env bash++. lib++rm -rf temp1 temp2++# this should fail, since temp1 doesn't exist...+not darcs get temp1 temp2++# verify that temp2 wasn't created+not cd temp2++rm -rf temp1 temp2
@@ -0,0 +1,67 @@+#!/usr/bin/env bash++set -ev+rm -rf temp1 temp2++# creating the fork point+mkdir temp1+cd temp1+darcs init+cat > foo << FOO+original - apple+original - banana+FOO+darcs add foo+darcs record -am init+cd ..+darcs get temp1 temp2++# do some work in the mainline+cd temp1+cat > foo << FOO+conflict 1 - artichoke+original - banana+FOO+darcs record -am 'conflict 1a'+cat > foo << FOO+conflict 1 - artichoke+conflict 1 - brocolli+FOO+darcs record -am 'conflict 1b'+cd ..++# do some work in the branch+cd temp2+cat > foo << FOO+conflict 2 - aardvark+original - banana+conflict 2 - cougar+FOO+darcs record -am 'conflict 2'+cd ..++# in the branch, pull from the mainline and resolve the conflict+cd temp2+darcs pull -a ../temp1 --allow-conflicts+cat > foo << FOO+resolution+original - apple+original - banana+FOO+darcs record -am 'resolve conflicts 2,1a,1b'+cd ..++# do some extra work in the mainline+cd temp1+cat > foo << FOO+original - apple+FOO+darcs record -am 'conflict 1c'+cd ..++# in the branch, pull from the mainline again+cd temp2+darcs pull -a ../temp1 --allow-conflicts+cd ..++rm -rf temp1 temp2
@@ -0,0 +1,67 @@+#!/usr/bin/env bash++set -ev+rm -rf temp1 temp2++# creating the fork point+mkdir temp1+cd temp1+darcs init+cat > foo << FOO+original - apple+original - banana+FOO+darcs add foo+darcs record -am init+cd ..+darcs get temp1 temp2++# do some work in the mainline+cd temp1+cat > foo << FOO+original - apple+conflict 1 - brocolli+FOO+darcs record -am 'conflict 1b'+cat > foo << FOO+conflict 1 - artichoke+original - banana+FOO+darcs record -am 'conflict 1a'+cd ..++# do some work in the branch+cd temp2+cat > foo << FOO+conflict 2 - aardvark+original - banana+conflict 2 - cougar+FOO+darcs record -am 'conflict 2'+cd ..++# in the branch, pull from the mainline and resolve the conflict+cd temp2+darcs pull -a ../temp1 --allow-conflicts+cat > foo << FOO+resolution+original - apple+original - banana+FOO+darcs record -am 'resolve conflicts 2,1a,1b'+cd ..++# do some extra work in the mainline+cd temp1+cat > foo << FOO+original - apple+FOO+darcs record -am 'conflict 1c'+cd ..++# in the branch, pull from the mainline again+cd temp2+darcs pull -a ../temp1 --allow-conflicts+cd ..++rm -rf temp1 temp2
@@ -0,0 +1,48 @@+#!/usr/bin/env bash+## Test for issue1057 - when pulling from a symlink to the current+## repository, Darcs should detect that it *is* the current repo.+##+## Copyright (C) 2008 Thorkil Naur+##+## Permission is hereby granted, free of charge, to any person+## obtaining a copy of this software and associated documentation+## files (the "Software"), to deal in the Software without+## restriction, including without limitation the rights to use, copy,+## modify, merge, publish, distribute, sublicense, and/or sell copies+## of the Software, and to permit persons to whom the Software is+## furnished to do so, subject to the following conditions:+##+## The above copyright notice and this permission notice shall be+## included in all copies or substantial portions of the Software.+##+## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+## EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+## MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND+## NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS+## BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN+## ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN+## CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+## SOFTWARE.++. lib++rm -rf temp+mkdir temp+cd temp++mkdir repo+cd repo+darcs init+cd ..++ln -s repo srepo+cd srepo+DIR=`pwd`+echo $DIR+not darcs pull --debug -a "$DIR" 2> out+cat out+grep 'Can.t pull from current repository' out+cd ..++cd ..+rm -rf temp
@@ -0,0 +1,21 @@+#!/usr/bin/env bash++. lib++if echo $OS | grep -i windows; then+ echo this test does not work on windows because+ echo windows does not have symlinks+ exit 0+fi++rm -rf temp1 temp2+mkdir temp1+ln -s temp1 temp2+cd temp2+darcs init+touch a b+DIR=`pwd`+darcs add "${DIR}/../temp1/a" # should work, just to contrast with the case below+darcs add "${DIR}/b" # this is the case we are testing for+cd ..+rm -rf temp1 temp2
@@ -0,0 +1,28 @@+#!/usr/bin/env bash+set -ev++DARCS_EDITOR=echo+export DARCS_EDITOR+export SENDMAIL=`which true`++rm -rf temp1 temp2+mkdir temp1 temp2++cd temp2+darcs init++# setup test+cd ../temp1+darcs init+touch foo bar+darcs add foo bar+darcs record -a -m add_foo_bar -A x++# Test that --cc is also printed as recipient in case of success+darcs send --author=me -a --to=random@random --cc=foo@example.com ../temp2 2>&1|grep -i foo@example.com++# Test that --cc is also printed as recipient in case of error+darcs send --author=me -a --to=random@random --cc=foo@example.com ../temp2 2>&1|grep -i foo@example.com++cd ..+rm -rf temp1 temp2
@@ -0,0 +1,37 @@+#!/usr/bin/env bash++. lib++rm -rf temp+mkdir temp+cd temp+darcs init+darcs changes++echo changes summary > _darcs/prefs/defaults+darcs changes+echo changes summary arg > _darcs/prefs/defaults+not darcs changes+echo ALL summary > _darcs/prefs/defaults+darcs changes+echo ALL summary arg > _darcs/prefs/defaults+not darcs changes++echo changes last 10 > _darcs/prefs/defaults+darcs changes+echo changes last > _darcs/prefs/defaults+not darcs changes+echo ALL last 10 > _darcs/prefs/defaults+darcs changes+echo ALL last > _darcs/prefs/defaults+not darcs changes++echo changes author me > _darcs/prefs/defaults+not darcs changes+echo ALL author me > _darcs/prefs/defaults+darcs changes+echo ALL unknown > _darcs/prefs/defaults+not darcs changes++cd ..+rm -rf temp
@@ -0,0 +1,10 @@+#!/usr/bin/env bash+set -ev+rm -rf temp1 temp1-h+mkdir temp1+cd temp1+darcs init --old-fashioned-inventory+cd ..+darcs get --hashed temp1 temp1-h+test -e temp1-h/_darcs/hashed_inventory+rm -rf temp1 temp1-h
@@ -0,0 +1,30 @@+#!/usr/bin/env bash+set -ev++rm -rf temp0 temp1+# sanity check+mkdir temp0+cd temp0+darcs init --old-fashioned+cd ..+darcs get --old-fashioned temp0 temp1+test -e temp1/_darcs/inventory+rm -rf temp0 temp1++# get from hashed+mkdir temp0+cd temp0+darcs init --hashed+cd ..+darcs get --old-fashioned temp0 temp1+test -e temp1/_darcs/inventory+rm -rf temp0 temp1++# get from darcs-2+mkdir temp0+cd temp0+darcs init --darcs-2+cd ..+darcs get --old-fashioned temp0 temp1 > log+grep 'Warning' log+rm -rf temp0 temp1
@@ -0,0 +1,41 @@+#!/usr/bin/env bash++. lib+# This test script is in the public domain.+++rm -rf temp1 temp2 temp3++mkdir temp1+cd temp1+darcs initialize+echo A > A+darcs add A+darcs record -a -m Aismyname+echo B > B+darcs add B+darcs record -a -m Bismyname++cd ..+darcs get temp1 temp2++cd temp2+darcs obliterate --last 1 -a+echo C > C+darcs add C+darcs record -a -m Cismyname+cd ..++mkdir temp3+cd temp3+darcs init+darcs pull -a -v --intersection ../temp1 ../temp2++darcs changes > out+cat out+grep Aismyname out+not grep Bismyname out+not grep Cismyname out+cd ..++rm -rf temp1 temp2 temp3
@@ -0,0 +1,31 @@+#!/usr/bin/env bash+set -ev++rm -rf temp-$$+mkdir temp-$$+cd temp-$$+set -e+darcs initialize+echo text > foo+darcs add foo+darcs rec -am 'add foo'+darcs annotate -p .++echo newtext > foo+darcs record -am 'modify foo'++darcs diff --store --last=1 > out1+cat out1+grep text out1+grep foo out1++darcs diff --last=1 > out+cat out+grep text out+grep foo out++diff -u out1 out++cd ..++rm -rf temp-$$
@@ -0,0 +1,29 @@+#!/usr/bin/env bash+set -ev++rm -rf temp-$$+mkdir temp-$$+cd temp-$$+set -e+darcs initialize+echo text > foo+darcs add foo+darcs record -am 'add foo'+echo newtext > foo+darcs wh++darcs diff --store > out1+cat out1+grep text out1+grep foo out1++darcs diff > out+cat out+grep text out+grep foo out++diff out out1++cd ..++rm -rf temp-$$
@@ -0,0 +1,16 @@+#!/bin/sh++set -ev++not () { "$@" && exit 1 || :; }++rm -rf temp+mkdir temp+cd temp+darcs init+not darcs add a/ 2> err+cat err+grep 'does not exist (No such file or directory)' err+cd ..++rm -rf temp
@@ -0,0 +1,20 @@+#!/usr/bin/env bash+## Test for issue1248 - darcs doesn't handle darcs 1 repos with compressed+## inventories+##+## Placed into the public domain by Ganesh Sittampalam, 2009++. lib+rm -rf temp1+mkdir temp1+cd temp1+darcs init --old-fashioned+touch foo+darcs rec -a -m'foo' -l --author=me@me+darcs tag --author=me@me foo+darcs check+darcs optimize --reorder+darcs check+darcs optimize --compress+darcs check+rm -rf temp1
@@ -0,0 +1,28 @@+#!/bin/sh++set -ev++not () { "$@" && exit 1 || :; }++rm -rf temp+rm -rf dist1269.tar.gz+mkdir temp+cd temp++darcs init+printf "Line1\nLine2\nLine3\n" > foo+darcs record -alm Base++darcs setpref predist false++# It is a bug in darcs if the dist succeeds. It should+# fail with a non-zero exit code+not darcs dist -d dist1269++# It is a bug in darcs if the dist file has been created+# it should /not/ be created if the predist cmd returned+# a non-zero exit code+not test -f dist1269.tar.gz++cd ..+rm -rf temp
@@ -0,0 +1,49 @@+#!/usr/bin/env bash+## Test for issue1373 - check that --token-chars [^ \t] is allowed.+## While we're at it, check some other things *aren't* allowed.+##+## Copyright (C) 2009 Trent W. Buck+##+## Permission is hereby granted, free of charge, to any person+## obtaining a copy of this software and associated documentation+## files (the "Software"), to deal in the Software without+## restriction, including without limitation the rights to use, copy,+## modify, merge, publish, distribute, sublicense, and/or sell copies+## of the Software, and to permit persons to whom the Software is+## furnished to do so, subject to the following conditions:+##+## The above copyright notice and this permission notice shall be+## included in all copies or substantial portions of the Software.+##+## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+## EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+## MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND+## NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS+## BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN+## ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN+## CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+## SOFTWARE.++. lib++rm -rf temp # Another script may have left a mess.+darcs init --repodir temp+replace () { darcs replace --repodir temp --token-chars "$1" x y; }+## These are not well-formed tokens.+not replace ''+not replace 'a'+not replace ']a['+not replace '[]'+not replace '[^]'+## These are well-formed, but allow tokens to contain whitespace.+not replace $'[ ]'+not replace $'[\t]'+not replace $'[\n]'+not replace $'[\r]'+not replace $'[\v]'+not replace $'[^ ]'+not replace $'[^\t]'+not replace $'[^\n]'+not replace $'[^\r]'+not replace $'[^\v]'+rm -rf temp # Clean up after ourselves.
@@ -0,0 +1,53 @@+#!/usr/bin/env bash+## Test for issue1446 - darcs amend-record -m foo destroys long description without warning+##+## Copyright (C) 2009 Dmitry Kurochkin+##+## Permission is hereby granted, free of charge, to any person+## obtaining a copy of this software and associated documentation+## files (the "Software"), to deal in the Software without+## restriction, including without limitation the rights to use, copy,+## modify, merge, publish, distribute, sublicense, and/or sell copies+## of the Software, and to permit persons to whom the Software is+## furnished to do so, subject to the following conditions:+##+## The above copyright notice and this permission notice shall be+## included in all copies or substantial portions of the Software.+##+## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+## EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+## MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND+## NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS+## BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN+## ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN+## CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+## SOFTWARE.++. lib # Load some portability helpers.+rm -rf R # Another script may have left a mess.+darcs init --repo R # Create our test repo.++touch R/f+darcs add f --repo R+echo 'patch name' > R/patchinfo+echo 'patch description' >> R/patchinfo+darcs record -a --logfile=R/patchinfo --repo R+darcs changes --repo R | grep 'patch name'+darcs changes --repo R | grep 'patch description'++echo 'y' | darcs amend-record -p 'patch name' -m 'new name' --repo R+darcs changes --repo R | grep 'new name'+darcs changes --repo R | grep 'patch description'++echo content > R/f+echo 'another name' > R/patchinfo+echo 'another description' >> R/patchinfo+darcs record -a --logfile=R/patchinfo --repo R+darcs changes --repo R | grep 'another name'+darcs changes --repo R | grep 'another description'++echo 'y' | darcs amend-record -p 'another name' -m 'one more name' --repo R+darcs changes --repo R | grep 'one more name'+darcs changes --repo R | grep 'another description'++rm -rf R/ # Clean up after ourselves.
@@ -0,0 +1,42 @@+#!/usr/bin/env bash+## Test for issue1465 - ortryrunning should try RHS if AND ONLY IF the+## LHS actually failed wasn't found or wasn't executable.+##+## Copyright (C) 2009 Trent W. Buck+##+## Permission is hereby granted, free of charge, to any person+## obtaining a copy of this software and associated documentation+## files (the "Software"), to deal in the Software without+## restriction, including without limitation the rights to use, copy,+## modify, merge, publish, distribute, sublicense, and/or sell copies+## of the Software, and to permit persons to whom the Software is+## furnished to do so, subject to the following conditions:+##+## The above copyright notice and this permission notice shall be+## included in all copies or substantial portions of the Software.+##+## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+## EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+## MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND+## NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS+## BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN+## ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN+## CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+## SOFTWARE.++. ../tests/lib # Load some portability helpers.+rm -rf R # Another script may have left a mess.+darcs init --repo R # Create our test repo.+mkdir R/d # Change the working tree.++# Set the editor to a program that just fails (VISUAL), make Darcs use+# it (--edit), and then make sure when it brokenly launches a fallback+# editor, that editor will exit because stdin isn't a tty (/dev/null).+not env -u TERM VISUAL=false </dev/null &>R/log \+darcs record --repo R -lam 'Initial commit.' --edit++# If Darcs did the right thing, the output won't make any mention of+# the fallback editors.+not egrep -i 'not found|vi|emacs|nano|edit' R/log++rm -rf R/ # Clean up after ourselves.
@@ -0,0 +1,48 @@+#!/usr/bin/env bash+## Test for issue154 - when applying a patch that removes a directory,+## don't remove the directory from the working tree unless it's empty.+##+## Copyright (C) 2008 Mark Stosberg+##+## Permission is hereby granted, free of charge, to any person+## obtaining a copy of this software and associated documentation+## files (the "Software"), to deal in the Software without+## restriction, including without limitation the rights to use, copy,+## modify, merge, publish, distribute, sublicense, and/or sell copies+## of the Software, and to permit persons to whom the Software is+## furnished to do so, subject to the following conditions:+##+## The above copyright notice and this permission notice shall be+## included in all copies or substantial portions of the Software.+##+## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+## EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+## MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND+## NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS+## BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN+## ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN+## CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+## SOFTWARE.++. lib++rm -rf temp1+mkdir temp1+cd temp1+darcs init+mkdir d+darcs add d+darcs record -a -m "Added directory d"+darcs get ./ puller+cd puller+touch d/moo+darcs add d/moo+cd ..+rm -rf d+darcs record -a -m "Remove directory d"+cd puller+echo y | darcs pull -a .. > log+grep -i "backing up" log+grep -i "finished pulling" log+cd ..+rm -rf temp1
@@ -0,0 +1,44 @@+#!/usr/bin/env bash++# issue157: A test for how rollback handles dependencies and conflicts.++. lib++rm -rf temp1 temp2++# set up the repository+mkdir temp1+cd temp1+darcs init --darcs-2+cd ..++# do some work here+cd temp1+echo a > foo+# Create three patches+darcs record -lam AA+echo b > foo+darcs record -lam BB+echo c > foo+darcs record -lam CC+# Rollback the last one+darcs rollback -a -p CC -m 'rollback CC'+grep b foo # reality check+cd ..++# Now get just the first two patches into a new repo...ignoring the third patch and the rollback.+darcs get --to-patch BB temp1 temp2+cd temp2+# Now in the second repo, rollback the second patch.+darcs rollback -a -p BB -m 'rollback BB'+grep a foo # reality check+# Finally, pull the third patch and the rollback of it.+# Because the two patches should cancel each other other, there should be no change here.+darcs pull -a ../temp1+# expect a conflict between the contents being 'a' or 'b'+grep "v v v" foo+grep a foo+grep b foo+cd ..++rm -rf temp1 temp2
@@ -0,0 +1,23 @@+#!/usr/bin/env bash++# issue174: behave better when we want to obliterate a patch that comes before a tag.+. lib++rm -rf temp1++mkdir temp1+cd temp1+darcs init+# Setup: Create a patch, a tag and another patch.+touch a.txt+darcs add a.txt+darcs record -am 'adding a' a.txt+darcs tag "first tag";+touch b.txt+darcs add b.txt+darcs record --ignore-time -a -m 'adding b' b.txt+darcs obliterate -p "adding a" -a > log+not grep "no patch" log+cd ..++rm -rf temp1
@@ -0,0 +1,19 @@+#!/usr/bin/env bash++. lib++# For issue184: recording files in directories that haven't explicity been added.++rm -rf temp1+mkdir temp1+cd temp1+darcs init+mkdir new+mkdir new/dir+touch new/dir/t.t+darcs add new/dir/t.t+darcs record -am test new/dir/t.t > log+not grep "don't want to record" log+cd ..++rm -rf temp1
@@ -0,0 +1,26 @@+set -ev++rm -rf temp1 temp2++mkdir temp1; cd temp1 ; darcs init ; cd ..+darcs get temp1 temp2+cd temp2/ ; echo 'x' > _darcs/prefs/author ; cd ..+cd temp1/ ; echo 'x' > _darcs/prefs/author ; cd ..++cd temp1/+touch test ; darcs record+darcs add test ; darcs record -a -m 'test'+darcs mv test best ; darcs record -a -m 'test -> best'+darcs mv best test ; darcs record -a -m 'best -> test'+cd ..++cd temp2/+touch test2+darcs add test2 ; darcs record -a -m 'test2'+darcs mv test2 best ; darcs record -a -m 'test2 -> best'+darcs mv best test2 ; darcs record -a -m 'best -> test2'++darcs pull ../temp1/ -a+cd ..++rm -rf temp1 temp2
@@ -0,0 +1,18 @@+#!/usr/bin/env bash+set -ev++# Issue244+# darcs changes should be able to pull up the history for a file+# using its moved and not-yet recorded new name++rm -rf temp1+mkdir temp1+cd temp1+darcs init+touch b+darcs add b+darcs record -am 11+darcs mv b c+darcs changes c | grep 11+cd ..+rm -rf temp1
@@ -0,0 +1,23 @@+#!/usr/bin/env bash+set -ev++rm -rf tempc+mkdir tempc+cd tempc+darcs init+echo foo > foo.c+darcs rec -Ax -alm init+cd ..+rm -rf temps+darcs get tempc temps+cd temps+echo server >> foo.c+darcs rec -Ax -alm server+cd ../tempc+echo client >> foo.c+darcs rec -Ax -alm client+if darcs push -a ../temps; then+ false+fi+cd ..+rm -rf tempc temps
@@ -0,0 +1,42 @@+#!/usr/bin/env bash++# issue279: a conflict case resulting in "bug in get_extra" with old+# format repos and "Malformed patch bundle" with darcs-2 repos.++. lib++rm -rf temp1 temp_a temp_b temp_c temp_d+mkdir temp1+cd temp1+darcs init --darcs-2+echo 0 > f+darcs add f+darcs record -am 00+cd ..++for r in a b c d; do+ darcs put --repodir temp1 temp_$r+ cd temp_$r;+ echo $r > f+ darcs record -am "patch:$r";+ cd ..+done++cd temp_d+darcs pull -a ../temp_a+darcs pull -a ../temp_b+darcs pull -a ../temp_c+cd ..+cd temp_c+darcs pull -a ../temp_a+darcs pull -a ../temp_b+echo rc > f+darcs record -a -m rc+cd ..+cd temp_d+darcs pull -a ../temp_c > log+not grep -i "no remote changes" log+not grep -i get_extra log+cd ..++rm -rf temp1 temp_a temp_b temp_c temp_d log
@@ -0,0 +1,50 @@+#!/usr/bin/env bash+set -ev++# for issue381: "darcs send -o message --edit-description doesn't work"++DARCS_EDITOR=echo+export DARCS_EDITOR++rm -rf temp1 temp2+mkdir temp1 temp2++cd temp2+darcs init+cd ..++cd temp1+darcs init+echo Hello world > foobar+darcs add foobar+darcs record -a -A me -m add_foobar++# Test that editor is called when --output is used with --edit-description+echo This is a note > note+cat > editor <<EOF+#!/usr/bin/env bash+echo I am running the editor+echo the file is \$1+mv \$1 \$1-temp+echo hello world >> \$1+cat \$1-temp >> \$1+echo >> \$1+echo finished editing >> \$1+echo I am done running the editor+EOF++chmod +x editor++DARCS_EDITOR='bash editor' darcs send --debug --author=me -a --output=bundle --edit-description ../temp2++echo === beginning of bundle > ===+cat bundle+echo === end of bundle > ===++grep ' add_foobar' bundle+grep 'finished editing' bundle++IFS=' ' darcs send --author=me -a --subject="it works" --to user@place.org --sendmail-command='grep "^Subject: it works$" %<' ../temp2++cd ..+rm -rf temp1 temp2
@@ -0,0 +1,30 @@+#!/usr/bin/env bash+set -ev++rm -rf temp1 temp2+mkdir temp1+cd temp1+# this test fails in the darcs 1 format+darcs init --darcs-2+echo A > f+darcs add f+darcs record --ignore-times -a -m A+cd ..++darcs get temp1 temp2++cd temp1+echo C > f+darcs record --ignore-times -a -m A-C+cd ..++cd temp2+echo B > f+darcs record --ignore-times -a -m A-B+echo A > f+darcs record --ignore-times -a -m B-A+(darcs push -a || :) 2> push-result+grep "Refusing to apply" push-result+cd ..++rm -rf temp1 temp2
@@ -0,0 +1,28 @@+#!/usr/bin/env bash+### http://bugs.darcs.net/issue458+### darcs get --set-scripts-executable ignores umask+set -ev++## Windows doesn't support proper permissions.++if echo $OS | grep -i windows; then+ echo Windows does not support posix permissions+ exit 0+fi++rm -rf temp+mkdir temp+cd temp++mkdir repo1+darcs initialize --repodir repo1+printf >repo1/x '#!/bin/sh\ntrue' # make a shebang'd script+darcs record --repodir repo1 -lam x x+umask 077 # DENY ALL access to group, all+darcs get --set-scripts-executable repo1 repo2+ls -l repo2/x | cut -f 1 -d\ > mode+echo -rwx------ > desired-mode+diff -u desired-mode mode++cd ..+rm -rf temp
@@ -0,0 +1,24 @@+#!/bin/sh++set -ev++## I would use the builtin !, but that has the wrong semantics.+not () { "$@" && exit 1 || :; }++rm -rf temp1+mkdir temp1+cd temp1+darcs init+echo first > a+darcs add a+darcs record -am 'first'+echo replace first with something else > a+darcs record -am 'mistake'+echo first > a+echo on second thought >> a+echo ya | darcs amend-record -a+darcs changes --last=1 -v > output+cat output+not grep first output+cd ..+rm -rf temp1
@@ -0,0 +1,27 @@+#!/usr/bin/env bash+++. lib++if echo $OS | grep -i windows; then+ echo This test does not work under Windows+ exit 0+fi++# pull from not empty repo to empty repo+rm -rf temp1+mkdir temp1++cd temp1+darcs init+echo a > Aux.hs+not darcs add Aux.hs+darcs add --reserved-ok Aux.hs+echo b > foo+darcs add foo+darcs record -am 'two files'+not darcs mv foo com1+darcs mv --reserved-ok foo com1+cd ..++rm -rf temp1
@@ -0,0 +1,117 @@+#!/bin/env bash+# A test for issue 538 - that an executable test script will run successfully if+# it is recorded with --set-scripts-executable.++set -ev++if echo $OS | grep -i windows; then+ echo I do not know how to run a test program under windows+ exit 0+fi++function make_repo_with_test {+ mkdir temp1 ; cd temp1 ; darcs init+ echo "#!/bin/sh" > test.sh+ echo "echo 'hello world'" >> test.sh+ darcs add test.sh+ darcs record --author=test@test -am test+ darcs setpref test './test.sh'+}++# test record with --set-scripts-executable+rm -rf temp1+make_repo_with_test+touch blaat+darcs add blaat+if darcs record --set-scripts-executable -A test@test -am blaat ; then+ echo "ok 1"+else+ echo "not ok 1 recording second patch failed (because test failed?)"+ exit 1+fi+cd ..++# test record without --set-scripts-executable +rm -rf temp1+make_repo_with_test+touch blaat+darcs add blaat+if darcs record --dont-set-scripts-executable -A test@test -am blaat ; then+ echo "not ok 2 recording second patch succeeded though test script should not be executable"+ exit 1+else+ echo "ok 2"+fi+cd ..++# test amend-record with --set-scripts-executable+rm -rf temp1+make_repo_with_test+touch blaat+darcs add blaat+if echo y | darcs amend-record --set-scripts-executable -A test@test -a ; then+ echo "ok 3"+else+ echo "not ok 3 amending patch failed (because test failed?)"+ exit 1+fi+cd ..++# test amend-record without --set-scripts-executable+rm -rf temp1+make_repo_with_test+touch blaat+darcs add blaat+if echo y | darcs amend-record --dont-set-scripts-executable -A test@test -a /dev/null ; then+ echo "not ok 4 amending patch succeeded even though --dont-set-scripts-executable specified"+ exit 1+else + echo "ok 4"+fi+cd ..++# trackdown with --set-scripts-executable+rm -rf temp1+make_repo_with_test+if darcs trackdown --set-scripts-executable | grep 'Success!' ; then+ echo "ok 5"+else+ echo "not ok 5 tracking down with --set-scripts-executable failed (because test failed?)"+ exit 1+fi+cd ..++# trackdown without --set-scripts-executable+rm -rf temp1+make_repo_with_test+if darcs trackdown --dont-set-scripts-executable | grep 'Noone passed the test!' ; then+ echo "ok 6"+else+ echo "not ok 6 tracking down did not find failure even though --dont-set-scripts-executable was given"+ exit 1+fi+cd ..++# check trackdown with files that become scripts during trackdown+rm -rf temp1+mkdir temp1 ; cd temp1 ; darcs init+echo "#!/bin/sh" > test.sh+echo "./helper.sh" >> test.sh+echo "#!/bin/sh" > helper.sh+echo "echo 'helper speaking'" >> helper.sh+darcs add test.sh+darcs add helper.sh+darcs record -am 'valid helper' -A test+echo 'this is definitely not a valid script' > helper.sh+darcs record -am 'invalid helper' -A test+darcs setpref test './test.sh'+darcs trackdown --set-scripts-executable > trackdown-out+if grep 'Test failed!' trackdown-out && grep 'Success!' trackdown-out ; then+ echo "ok 7"+else + echo "not ok 7 either no failure or no success (both should occur)"+ exit 1+fi+cd ..++rm -rf temp1
@@ -0,0 +1,32 @@+#!/usr/bin/env bash++# For issue588, "amend-record --look-for-adds end up with two "addfile" entries"++set -ev++rm -rf temp1+darcs init --repodir temp1++# Setup f with contents foo.+echo foo > temp1/f+darcs add --repodir temp1 f+darcs rec --repodir temp1 -am p1++# Remove f, and amend p1, but only the hunk not the rmfile.+# Here we use look-for-adds to trigger the bug+rm temp1/f+echo yyd | darcs amend-record --repodir temp1 --look-for-adds++darcs changes --repodir temp1 --last 1 -v++echo show the buggy pending+cat temp1/_darcs/patches/pending++echo bar > temp1/f+echo y | darcs amend-record --repodir temp1 --all++darcs changes --repodir temp1 --last 1 -v++darcs check --repodir temp1 --no-test++rm -rf temp1
@@ -0,0 +1,50 @@+#!/usr/bin/env bash++# Issue595+#+# A test for running "darcs get" when the parent directory has restrictive+# permissions. The bug is that darcs trys to "chdir" to the current directory+# using the full path. The permissions on the parent directory prevent this+# from working, even though the current repo and the remote have sufficient+# permissions.+#+# The real-world case where this would happen would be a web-server with+# restrictive permissions on "/home", with a user running darcs within that.++. lib++abort_windows++rm -rf temp1 temp2++# Set up a "remote" repo+mkdir tmp_remote+cd tmp_remote+darcs 'init'+cd ..++DIR=`pwd`+# Set up a directory with restrictive permissions+mkdir -p tmp_restrictive/liberal+cd tmp_restrictive/liberal+chmod 0111 ../../tmp_restrictive+# sanity check that we can cd out and back+cd ../..; cd tmp_restrictive/liberal+# TODO: we avoid this test on Solaris because it seems we can't create+# anything in tmp_restrictive/liberal+touch can_touch+if [ -e can_touch ]; then+ if hwpwd; then+ darcs get "$DIR/tmp_remote" 2> log+ not grep -i 'permission denied' log+ else+ echo "Apparently I can't do `basename $0` on this platform"+ fi+else+ echo "Can't do `basename $0` on this platform"+fi+cd "$DIR"+# We have to fix the permissions, just so we can delete it.+chmod 0755 tmp_restrictive++rm -rf tmp_remote tmp_restrictive
@@ -0,0 +1,42 @@+#!/usr/bin/env bash++# Test that darcs fails appropriately when the target repo inventory file is not writable.+# See issue612++. lib++abort_windows++if grep old-fashioned .darcs/defaults; then+ echo This test does not work with old-fashioned inventories+ exit 0+fi++rm -rf temp1 temp2+mkdir temp1+cd temp1+darcs init+touch t.t+darcs add t.t+darcs record -am "initial add"+if [ -e _darcs/inventories ]; then+ chmod 0555 _darcs/inventories/*+ chmod 0555 _darcs/inventories+fi+if [ -e _darcs/inventory ]; then+ chmod 0555 _darcs/inventory+fi+cd ..++darcs get temp1 temp2+cd temp2+echo new >> t.t+darcs record -am "new patch"+not darcs push -a ../r1 2> log+grep failed log+cd ..++chmod -R 0755 temp1+rm -rf temp1 temp2++
@@ -0,0 +1,12 @@+#!/usr/bin/env bash+set -ev++rm -rf temp+mkdir temp+cd temp+darcs init+echo 'record patch-name' > _darcs/prefs/defaults # patch-name requires an argument+echo 'ALL unified foobar' >> _darcs/prefs/defaults # unified takes no argument+darcs record && exit 1+darcs whats && exit 1+rm -rf temp
@@ -0,0 +1,22 @@+#!/usr/bin/env bash+set -ev++# for issue706: "Filenames with spaces issue"++DARCS_EDITOR=echo+export DARCS_EDITOR++rm -rf temp+mkdir temp+cd temp+darcs init++touch 'A B'++darcs add 'A B'+darcs rec -a -m 'a b' -A me+ls+darcs check++cd ..+rm -rf temp
@@ -0,0 +1,56 @@+#!/bin/sh+set -ve++. lib++rm -rf temp1+mkdir temp1+cd temp1+darcs init++# Here we check whether recording just one of two --look-for-add+# addfiles causes any trouble (which it doesn't)++date > f1+date > f2+echo yyd | darcs record -l -m ff++cat _darcs/patches/pending++not darcs wh+rm f2++# Try recording a file add without --look-for-adds, with a setpref+# patch present that we don't record.++darcs setpref boringfile .boring++echo bar > bar+darcs add bar+echo yyd | darcs record -mbar++cat _darcs/patches/pending++darcs whatsnew -s++test -z "`darcs whatsnew -s`"++# Now try the same thing using --look-for-adds++echo foo > foo++darcs wh -l++# remove any files added by profiling or hpc...+rm -f darcs.tix darcs.prof++echo yyd | darcs record --look-for-adds -mfoo++cat _darcs/patches/pending++darcs whatsnew -s++test -z "`darcs whatsnew -s`"++cd ..+rm -rf temp1
@@ -0,0 +1,62 @@+#!/usr/bin/env bash+## Test for issue70 - unrecorded (pending) changes to preferences+## should coalesce; if you set the same preference more than once in+## the same patch, only the last change should actually be recorded.+##+## Copyright (C) 2005 Eric Kow+##+## Permission is hereby granted, free of charge, to any person+## obtaining a copy of this software and associated documentation+## files (the "Software"), to deal in the Software without+## restriction, including without limitation the rights to use, copy,+## modify, merge, publish, distribute, sublicense, and/or sell copies+## of the Software, and to permit persons to whom the Software is+## furnished to do so, subject to the following conditions:+##+## The above copyright notice and this permission notice shall be+## included in all copies or substantial portions of the Software.+##+## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+## EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+## MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND+## NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS+## BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN+## ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN+## CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+## SOFTWARE.++. lib++rm -rf temp1+mkdir temp1+cd temp1+darcs init+darcs setpref predist apple+darcs setpref predist banana+darcs setpref predist clementine+darcs record -a -m manamana+darcs changes --verbose > log+not grep apple log+not grep banana log+grep clementine log+cd ..+rm -rf temp1++# not sure what i'm going for here - if coalescing happens strictly+# before commuting, no problem, but what if patches are commuted+# before coalescing?+mkdir temp1+cd temp1+darcs init+darcs setpref predist apple+darcs setpref predist banana+darcs setpref predist apple+darcs setpref predist clementine+darcs setpref predist banana+darcs record -a -m manamana+darcs changes --verbose > log+not grep apple log+not grep clementine log+grep banana log+cd ..+rm -rf temp1
@@ -0,0 +1,31 @@+#!/usr/bin/env bash++# http://bugs.darcs.net/issue803: Darcs 2.0 regression on manual renames+++. lib++rm -rf temp+mkdir temp+cd temp+darcs init++touch a.txt+darcs add a.txt+darcs record -a -m "First" -A me++mkdir subdir+darcs add subdir+darcs record -a -m "Second" -A me++mv a.txt subdir/+darcs mv a.txt subdir/a.txt+darcs record -a -m "Third" -A me++darcs changes --last 1 -v > stdout+cat stdout++not grep 'rmfile' stdout++cd ..+rm -rf temp
@@ -0,0 +1,20 @@+#!/usr/bin/env bash+set -ev+rm -rf temp1 temp2+mkdir temp1+cd temp1+darcs init+echo > a+darcs add a+darcs record a -a -m "init"+cd ..++mkdir temp2+cd temp2+darcs init+darcs pull ../temp1 -a+darcs optimize --compress+for f in _darcs/patches/*-*; do+ gzip -t < "$f"+done+rm -rf temp1 temp2
@@ -0,0 +1,57 @@+#!/usr/bin/env bash++# Issue 864++# darcs mv file1 file2+# edit file2+# darcs replace fails if new token includes some existing edits++# Regression in darcs2 - it works in darcs1 1.0.9++set -ev++rm -rf temp+mkdir temp+cd temp+darcs init+cat <<EOF > file1+aa+bb+cc+aa+bb+cc+EOF+darcs add file1+darcs record -a -m "0" --author X++cat <<EOF > file1+aaaa+bb+cc+aa+bb+cc+EOF++darcs replace aa aaaa file1 > out+cat out+grep 'Skipping file' out && exit 1++darcs mv file1 file2++cat <<EOF > file2+aaaa+beebee+cc+aa+bb+cc+EOF++darcs replace bb beebee file2 > out+cat out+grep 'Skipping file' out && exit 1++cd ..+rm -rf temp
@@ -0,0 +1,34 @@+#!/bin/sh++# Issue885: Regression: "darcs get --to-match" does not work anymore under 2.0+++. lib++rm -rf temp1+mkdir temp1+cd temp1+darcs init+echo first > a+darcs add a+darcs record -am 'first'+firsthash=`darcs changes --xml | grep 'hash=' | sed -e "s/.*hash='//" -e "s/'>//"`+echo second > b+darcs add b+darcs record -am 'second'++# Pulling that patch works ok+cd ..+rm -rf temp2+mkdir temp2+cd temp2+darcs init+echo darcs pull -v -a --match "hash $firsthash" ../temp1+darcs pull -v -a --match "hash $firsthash" ../temp1++# Getting up-to that patch does not+cd ..+rm -rf temp3+echo darcs get -v --to-match "hash $firsthash" temp1 temp3+darcs get -v --to-match "hash $firsthash" temp1 temp3+
@@ -0,0 +1,25 @@+#!/usr/bin/env bash+set -ev++rm -rf temp+mkdir temp; cd temp+darcs init+echo "aaa diff" > file+darcs add file+darcs record -a -m "aaa"+echo "bbb diff" >> file+darcs record -a -m "bbb"++darcs diff --patch "aaa" | grep "aaa diff"+darcs diff --patch "bbb" | grep "bbb diff"++darcs tag release-1+darcs optimize++echo "ccc diff" >> file+darcs record -a -m "ccc"+darcs diff --patch "ccc" | grep "ccc diff"++# here is where we have a problem+darcs diff --patch "aaa" | grep "aaa diff"+darcs diff --patch "bbb" | grep "bbb diff"
@@ -0,0 +1,15 @@+#!/usr/bin/env bash+set -ev++rm -rf temp1+mkdir temp1+cd temp1+darcs init+touch foo+darcs add foo+darcs record -a -m add_foo -A x+rm foo+darcs whatsnew+cd ..+rm -rf temp1+
@@ -0,0 +1,64 @@+#!/usr/bin/env bash++. lib++rm -rf temp1 temp2 temp3+mkdir temp1+cd temp1+darcs init++# this test only applies to hashed formats+if cat _darcs/inventory; then exit 0; fi++date > f1+darcs add f1+darcs record -am 'add f1'++darcs tag -m 'tag f1'++date > f2+darcs add f2+darcs record -am 'add f2'+cd ..++darcs get --lazy temp1 temp2++darcs get --lazy temp2 temp3++cd temp2++# Run darcs changes so we pull in the inventories (but no the patches)+darcs changes++# Remove original repository, so we have no references to changes f1 and f2.+rm -rf ../temp1++# Now we should be unable to read some of the history+darcs changes -s > out+cat out+grep unavailable out++date > f3+darcs add f3+darcs record -am 'add f3'++darcs tag -m 'tag 123'+cd ..++cd temp3+date > f4+darcs add f4+darcs record -am 'add f4'+darcs pull -av++# Here's the point of this test: we should be able to optimize+# --reorder without being able to read all the patches in the+# repository.+darcs optimize --reorder++# Just a double-check: we shouldn't be able to check in this case.+not darcs check++cd ..++rm -rf temp1 temp2 temp3 temp4
@@ -0,0 +1,18 @@+# This is a -*- sh -*- library.+set -ev++## I would use the builtin !, but that has the wrong semantics.+not () { "$@" && exit 1 || :; }++# trick: OS-detection (if needed)+abort_windows () {+if echo $OS | grep -i windows; then+ echo This test does not work on Windows+ exit 0+fi+}++pwd() {+ ghc --make "$TESTS_WD/hspwd.hs"+ "$TESTS_WD/hspwd"+}
@@ -0,0 +1,37 @@+#!/usr/bin/env bash++cat > empty_pending <<EOF+{+}+EOF+check_empty_pending() {+ echo ================ pending ==================+ cat _darcs/patches/pending+ echo ================ pending ==================+ [ -z _darcs/patches/pending ] || cmp _darcs/patches/pending ../empty_pending+}++set -ev++rm -rf temp1 temp2+mkdir temp1 temp2+cd temp1+darcs init+mkdir dir+darcs record -a -m add_dir -A x --look-for-adds+check_empty_pending+echo zig > dir/foo+echo zag > foo+mkdir dir2+echo hi > dir2/foo2+darcs record -a -m add_foo -A x --look-for-adds+check_empty_pending+cd ../temp2+darcs init+darcs pull -a ../temp1+cd ..+cmp temp1/dir2/foo2 temp2/dir2/foo2+cmp temp1/dir/foo temp2/dir/foo+cmp temp1/foo temp2/foo+rm -rf temp1 temp2+
@@ -0,0 +1,38 @@+#!/usr/bin/env bash++# Automated tests for "darcs mark-conflicts".++# The builtin ! has the wrong semantics for not.+not () { "$@" && exit 1 || :; }++mkdir temp1+cd temp1+darcs init+echo "Conflict, Base ." > child_of_conflict+darcs add child_of_conflict+darcs record -am 'Conflict Base'+cd ..+darcs get temp1 temp2++# Add and record differing lines to both repos+cd temp1+echo "Conflict, Part 1." > child_of_conflict+darcs record -A author -am 'Conflict Part 1'+cd ..+cd temp2+echo "Conflict, Part 2." > child_of_conflict+darcs record -A author -am 'Conflict Part 2'+cd ..++cd temp1+darcs pull -a ../temp2 > log+grep conflict log+grep finished log+grep 'v v' child_of_conflict+darcs revert -a+not grep 'v v' child_of_conflict+darcs mark-conflicts+grep 'v v' child_of_conflict+cd ..++rm -rf temp1 temp2
@@ -0,0 +1,196 @@+#!/usr/bin/env bash++. lib++rm -rf temp1+# Some tests for the '--match' flag, specifically the date-matching++reset_repo () {+ cd ..+ rm -rf temp1+ mkdir temp1+ cd temp1+ darcs init+ touch bar+ darcs add bar+}++create_entry () {+ echo $1 >> bar+ echo "$1/tester/a///" | tr / \\012 | darcs record -m "" --pipe bar+}++create_entry_now () {+ echo today >> bar+ darcs record -m "" bar -a -A tester+}++# parse_date just checks for parsing, while match_date checks for an actual match.+# It's better if we can use "match_date", but we have to be able to construct such a date+# based on a date dynamically generated by this test script.++# alternately, it might be more useful to build a random date string generator+# using QuickCheck... for any n random CalendarTimes, have it generate some+# possible variants and roundtrip them to see if they match++parse_date () {+ darcs changes --match "date \"$1\"" > log+ not grep fancy log+}++match_date () {+ darcs changes --match "date \"$1\"" > log+ grep tester log+ not grep fancy log+}++nomatch_date () {+ darcs changes --match "date \"$1\"" > log+ not grep tester log+ not grep fancy log+}++mkdir temp1+cd temp1+reset_repo++# this block of dates should all refer to the same thing+year=1973+mm=02+dd=04+hhmmss="15:08"+create_entry "$year-$mm-$dd $hhmmss"+echo "/tester/a///" | tr / \\012 | darcs record -m "" --pipe bar+match_date "$year-$mm-$dd"+match_date "$year$mm$dd"+match_date "$year-$mm"+match_date "$year$mm"+match_date "$year"++# week dates. note that 2007 was selected as it starts on Monday+reset_repo+create_entry "2007-01-04 15:00"+match_date '2007-W01-4'+nomatch_date '2007-W01-1'+match_date '2007W014'+match_date '2007-W01'+nomatch_date '2007-W02-1'+create_entry "2007-01-08 15:00"+match_date '2007-W02'+match_date '2007-W02-1'+create_entry "2007-05-20 15:00"+match_date '2007-W20'+nomatch_date '2007-W21'+nomatch_date '2007-W19'+# ordinal dates. eh... why not?+match_date '2007-004'; # fourth day of 2007+match_date '2007004'+nomatch_date '2007-005'++# midnight and zero+reset_repo+create_entry "1992-10-15 00:00"+match_date '1992-10-14 24:00'+match_date '1992-10-15 00:00'++# all the same date/time+reset_repo+create_entry "1992-02-12T22:32:11"+match_date '1992-02-12T22:32:11'+match_date '1992-02-12 22:32:11'+match_date '1992-02-12T223211.0000'++# english dates - the old hard coded from < darcs 1.0.6+reset_repo+year=`date +%Y`+mm=`date +%m`+dd=`date +%d`+hhmmss=`date +%k:%M:%S`+tz=`date +%z`+create_entry "$(($year-1))-$mm-$dd"+nomatch_date 'today'+nomatch_date 'yesterday'+nomatch_date 'day before yesterday'+nomatch_date 'last week'+nomatch_date 'last month'+# note: this test might fail if you run it just before midnight+reset_repo+create_entry_now+match_date 'today'+nomatch_date 'yesterday'+nomatch_date 'day before yesterday'+match_date 'last week'+match_date 'last month'++reset_repo+create_entry "$(($year-1))-$mm-$dd"+# english dates - new possibilities+nomatch_date 'yesterday at 14:00:00'+match_date 'last 3 years'+match_date 'last year'+nomatch_date '2 days ago'+nomatch_date 'last month 13:00'+nomatch_date '3 days before last week'+reset_repo+create_entry_now+match_date 'day after yesterday'+match_date 'week after last week'+create_entry "1992-10-02 00:15"+match_date '15 minutes after 1992-10-02'+reset_repo+create_entry "1992-10-02 00:15+05"+# note that earlier dates will always match+match_date '15 minutes after 1992-10-02 00:00+05'; # same time+match_date '15 minutes after 1992-10-01 23:00+04'; # same time+nomatch_date '15 minutes after 1992-10-02 01:00+05'; # 1 hour later+nomatch_date '15 minutes after 1992-10-02 00:00+04'; # 1 hour later+nomatch_date '1 hour, 15 minutes after 1992-10-02 00:00+05'; # 1 hour later+match_date '1 hour, 15 minutes after 1992-10-02 00:00+06'; # same time+match_date '1 hour, 15 minutes after 1992-10-01 23:00+05'; # same time++reset_repo+create_entry_now+create_entry 1992-10-02 00:15+# english intervals+nomatch_date 'between last fortnight and day before yesterday'+match_date 'between last fortnight and today'+match_date 'in the last 45 seconds'+match_date 'after 1992'++# iso 8601 intervals+parse_date '1992-10-02 00:00Z/1992-10-02 00:16Z'+match_date '1992-10-02 00:00/1992-10-02 00:16'+match_date 'between 1992-10-02 00:00 and 1992-10-12 00:16'+parse_date 'P3YT3M/1992'+parse_date '1992/P3Y3M4DT5H3M2S'+parse_date '1992/P3Y3M'++# stuff from the manual+reset_repo+create_entry_now+nomatch_date 'between 2004-03-12 and last week'+match_date 'last week'+parse_date 'yesterday'+parse_date 'today 14:00'+nomatch_date '3 days before last year at 17:00'+# We can't in general parse the raw date output by darcs. If we change the+# format to not include timezone information, this would be possible. But+# maybe that's not desireable. For now, we just won't test the raw date.+#match_date "$raw_date"+parse_date 'after 2005'+parse_date 'in the last 3 weeks'+parse_date 'P3M/2006-03-17'+parse_date '2004-01-02/2006-03-17'+parse_date 'P2M6D'++# cvs dates+parse_date '2006/01/19 21:14:20 UTC'+# We can't handle all timezones in the old style dates+# so this test will not work everywhere+# match_date "$year/$mm/$dd $hhmmss $tz"++reset_repo+create_entry '2038-01-01'+match_date 'after 2037'++rm -rf temp1 temp2
@@ -0,0 +1,97 @@+#!/usr/bin/env bash++# Some tests for the '--match' flag++. lib++# set up the repository+rm -rf temp1 # another script may have left a mess.+mkdir temp1+cd temp1+darcs init+cd ..++# create three patches - the property we exploit to determine+# if a matcher does the right thing is that each patch has a+# different author+cd temp1+touch bar+darcs add bar+darcs record -a -m "first patch" bar -A author1+echo foo > bar+darcs record -a -m "\"second\" \\ patch" bar -A author2+echo blop > bar+darcs record -a -m "second" bar -A author3+cd ..++# -------------------------------------------------------------------+# single matchers+# -------------------------------------------------------------------++cd temp1+# matching on author really matches on that, and not something else+darcs changes --match='author "first patch"' > log+not grep '.' log+# normal changes shows both authors and patch names+darcs changes > log+grep author1 log+grep author2 log+grep author3 log+grep 'first patch' log+grep '"second" \\ patch' log+grep -v patch log | grep second+# exact+darcs changes --match='exact second' > log+not grep author1 log+not grep author2 log+grep author3 log+# name+darcs changes --match='name second' > log+not grep author1 log+grep author2 log+grep author2 log+# author+darcs changes --match='author author1' > log+grep author1 log+not grep author2 log+not grep author3 log+#hash+darcs changes --xml-output --match='exact "\"second\" \ patch"' > log+hash=`grep hash log | sed -e "s/.*hash='//" -e "s/'.*//"`+echo $hash+darcs changes --match="hash $hash"+not grep author1 log+grep author2 log+not grep author3 log+cd ..++# -------------------------------------------------------------------+# matching on combinations+#+# uses the setup from the atomic patches+# -------------------------------------------------------------------++cd temp1+# or+darcs changes --match='author author1 || author author2' > log+grep author1 log+grep author2 log+not grep author3 log+# and+darcs changes --match='name second && author author2' > log+not grep author1 log+grep author2 log+not grep author3 log+# not+darcs changes --match='not name second' > log+grep author1 log+not grep author2 log+not grep author3 log+# grouping+darcs changes --match='(not name second) || (author author3)' > log+grep author1 log+not grep author2 log+grep author3 log+cd ..++rm -rf temp1
@@ -0,0 +1,52 @@+#!/usr/bin/env bash+set -ev++rm -rf tempOld tempA tempB+mkdir tempOld tempA+cd tempOld+darcs initialize+echo record author me > _darcs/prefs/defaults+echo ALL all >> _darcs/prefs/defaults+#echo ALL verbose >> _darcs/prefs/defaults+echo ALL ignore-times >> _darcs/prefs/defaults+echo A > foo+echo B >> foo+echo C >> foo+echo D >> foo+echo E >> foo+echo F >> foo+echo G >> foo+echo H >> foo+darcs add foo+darcs record -m Old+cd ..++cd tempA+darcs initialize+cp ../tempOld/_darcs/prefs/defaults _darcs/prefs+darcs pull ../tempOld+cp foo temp+cat temp | grep -v A | grep -v B | grep -v D | sed s/E/e/ \+ | grep -v G | sed s/H/h/ > foo+darcs record -m AA+cd ..++darcs get tempOld tempB+cd tempB+cp ../tempOld/_darcs/prefs/defaults _darcs/prefs+echo 7 > foo+darcs record -m BB+darcs pull ../tempA+darcs record -m "conflict resolution"+cd ..++cd tempA+darcs pull ../tempB+darcs annotate -p B+darcs annotate -p resolution+cd ..++cmp tempA/foo tempB/foo++rm -rf tempOld tempA tempB+
@@ -0,0 +1,57 @@+#!/usr/bin/env bash+set -ev++rm -rf fooOld tempA tempB+mkdir fooOld tempA tempB+cd fooOld+darcs init+echo record author me > _darcs/prefs/defaults+echo ALL all >> _darcs/prefs/defaults+#echo ALL verbose >> _darcs/prefs/defaults+echo ALL ignore-times >> _darcs/prefs/defaults+echo Old > foo+darcs add foo+darcs record -m Old+cd ..++cd tempA+darcs init+cp ../fooOld/_darcs/prefs/defaults _darcs/prefs+darcs pull ../fooOld+echo A > foo+darcs record -m AA+cd ..++cd tempB+darcs init+cp ../fooOld/_darcs/prefs/defaults _darcs/prefs+darcs pull ../fooOld+echo B > foo+darcs record -m BB+darcs pull ../tempA+echo A > foo+darcs record -m "ok A's fine."+cd ..++# At this point, tempB and tempA should agree--since the conflict was+# resolved in favor of tempA.+cmp tempB/foo tempA/foo++cd tempA+echo AA > foo+darcs record -m "AA -- upping the ante."+cd ..++cd tempB+darcs pull ../tempA+cd ..++cd tempA+darcs pull ../tempB+cd ..++# At this point, tempB and tempA should agree since we have pulled both ways.+cmp tempB/foo tempA/foo++rm -rf fooOld tempA tempB+
@@ -0,0 +1,40 @@+#!/usr/bin/env bash++# trick: requiring something to fail+. lib++# A test for darcs resolve detecting a conflict, inspired by bug #152 in RT++rm -rf temp1 temp2++# set up the repository+mkdir temp1+cd temp1+darcs init+cd ..++cd temp1+echo "apply allow-conflicts" > _darcs/prefs/defaults+# note: to make this pass, change echo to echo -n+# is that right?+echo "from temp1" > one.txt+darcs add one.txt+darcs record -A bar -am "add one.txt"+echo >> one.txt+darcs wh -u+cd ..++darcs get temp1 temp2+cd temp2+# reality check+darcs show files | grep one.txt+echo "in tmp2" >> one.txt+darcs whatsnew -s | grep M+darcs record -A bar -am "add extra line"+darcs annotate -p . -u+darcs push -av > log+cat log+not grep -i conflicts log+cd ..++rm -rf temp1 temp2
@@ -0,0 +1,136 @@+#!/usr/bin/env bash++# Some tests for 'darcs mv'++. lib++rm -rf temp+mkdir temp+cd temp+darcs init++###++echo adding a directory with more than one .. in it should work.+mkdir foo.d+mkdir foo.d/second+mkdir foo.d/second/third+mkdir foo.d/other+++touch ./foo.d/other/date.t+darcs add -r foo.d++cd foo.d/second/third++darcs mv ../../other/date.t ../../other/date_moved.t++cd ../../..+echo darcs refuses to move to an existing file+touch ping+touch pong+darcs add ping pong+not darcs mv ping pong &> out+cat out+grep 'already exists' out++# case sensitivity series+# -----------------------+# these are tests designed to check out darcs behave wrt to renames+# where the case of the file becomes important++# are we on a case sensitive file system?+touch is_it_cs+rm -f IS_IT_CS++if test -e is_it_cs; then+ echo This is a case-sensitive file system.+else+ echo This is NOT a case-sensitive file system.+fi++# if the new file already exists - we don't allow it+# basically the same test as mv ping pong, except we do mv ping PING+# and both ping and PING exist on the filesystem+echo "case sensitivity - simply don't allow mv if new file exists"+touch 'cs-n-1'; touch 'CS-N-1';+touch 'cs-y-1'; touch 'CS-Y-1';+darcs add cs-n-1 cs-y-1++if test -e is_it_cs; then+ # regardless of case-ok, we do NOT want this mv at all+ not darcs mv cs-n-1 CS-Y-1 &> out+ cat out+ grep 'already exists' out++ not darcs mv --case-ok cs-n-1 CS-Y-1 &> out+ cat out+ grep 'already exists' out+fi++# if the new file does not already exist - we allow it+echo "case sensitivity - the new file does *not* exist"+touch 'cs-n-2';+touch 'cs-y-2';+darcs add cs-n-2 cs-y-2+# these mv's should be allowed regardless of flag or filesystem+darcs mv cs-n-2 CS-N-2+darcs mv --case-ok cs-y-2 CS-Y-2++# parasites - do not accidentally overwrite a file just because it has a+# similar name and points to the same inode. We want to check if a file if the+# same NAME already exists - we shouldn't care about what the actual file is!+echo "case sensitivity - inode check";+touch 'cs-n-3';+touch 'cs-y-3';+darcs add cs-n-3 cs-y-3++if ln cs-n-3 CS-N-3; then # checking if we support hard links+ ln cs-y-3 CS-Y-3+ # regardless of case-ok, we do NOT want this mv at all+ not darcs mv cs-n-3 CS-N-3 &> out+ cat out+ grep 'already exists' out++ not darcs mv --case-ok cs-y-3 CS-Y-3 &> out+ cat out+ grep 'already exists' out+fi++# parasites - we don't allow weird stuff like mv foo bar/foo just because+# we opened up some crazy exception based on foo's name+echo 'refuses to move to an existing file with same name, different path'+touch 'cs-n-4'; touch 'foo.d/cs-n-4';+touch 'cs-y-4'; touch 'foo.d/cs-y-4';+darcs add cs-n-4+# regardless of case-ok, we do NOT want this mv at all+not darcs mv cs-n-4 foo.d/cs-n-4 &> out+cat out+grep 'already exists' out++not darcs mv --case-ok cs-y-4 foo.d/cs-y-4 &> out+cat out+grep 'already exists' out++# ---------------------------+# end case sensitivity series++touch abs_path.t+darcs add abs_path.t+REPO_ABS=`pwd`+darcs mv "$REPO_ABS/abs_path.t" abs_path_new.t+darcs mv abs_path_new.t "$REPO_ABS/abs_path.t"+++# issue608++ touch 'gonna_be_deleted';+ darcs add gonna_be_deleted+ darcs record -am 'added doomed file'+ rm gonna_be_deleted+ darcs record -am 'deleted file'+ touch 'new_file';+ darcs add new_file+ darcs mv new_file gonna_be_deleted++
@@ -0,0 +1,28 @@+#!/usr/bin/env bash++set -ev++rm -rf temp1+mkdir temp1+cd temp1+darcs init++date > foo+darcs record -a -m add_foo -A x --look-for-adds++# add a test file+echo 'test ! -e foo' > test.sh+darcs add test.sh+darcs record -a -m add_test++darcs setpref test 'ls && bash test.sh'+darcs record -a -m settest -A x --no-test++darcs mv foo bar+darcs record --debug -a -m mvfoo -A x++darcs check++cd ..+rm -rf temp1+
@@ -0,0 +1,53 @@+#!/usr/bin/env bash+set -ev++rm -rf temp+mkdir temp+cd temp+darcs init+echo hi world > temp.c+darcs add temp.c+darcs record --all -A test --patch-name=hi+echo goodbye >> temp.c+darcs whatsnew+darcs record -a -A au -m bye+echo bar > bar.c+darcs add bar.c+darcs record -a -m one -A ex+darcs mv bar.c zig.c+darcs whatsnew+darcs record -a -m two -A ex+mkdir baz+darcs add baz+darcs whatsnew+darcs record -a -m three -A ex+darcs mv zig.c baz/bar.c+darcs whatsnew+darcs record -a -m four -A ex+darcs mv baz temp+darcs whatsnew+darcs record -a -m five -A ex++darcs mv temp temp 1> stdout 2> stderr || true+grep 'Cannot rename a file or directory onto itself' stderr++cd ..++rm -rf temp+mkdir temp+cd temp+darcs init+echo hi world > a+darcs add a+darcs record --all -m lower+cd ..+darcs get temp temp1+cd temp+darcs mv a A+echo goodbye > A+darcs record --all -m 'to upper'+cd ../temp1+darcs pull -a++cd ..+rm -rf temp temp1
@@ -0,0 +1,50 @@+#!/usr/bin/env bash+set -ev++rm -rf temp+mkdir temp+cd temp++darcs init+touch fee fi fo fum+darcs add f*+darcs record --author me --all --no-test --patch-name add+mkdir d+darcs add d+darcs mv f* d+darcs remove d/fi+cd d+darcs remove fo+echo let us have fun > fun+darcs add fun+darcs mv fun fum ..+darcs record --author me --all --no-test --patch-name mv+cd ..++if darcs show files | egrep '^./fee$'; then false; else true; fi+test ! -f fee+darcs show contents d/fee | cmp d/fee -++test ! -f fi+test -f d/fi+if darcs show files | egrep '^./fi$'; then false; else true; fi+if darcs show files | egrep '^./d/fi$'; then false; else true; fi++test ! -f fo+test -f d/fo+if darcs show files | egrep '^./fo$'; then false; else true; fi+if darcs show files | egrep '^./d/fo$'; then false; else true; fi++darcs show contents fun | cmp fun -+darcs show contents fum | cmp fum -++darcs mv fun d+darcs record -A me -a --no-test -m "fun again"+darcs show content d/fun | cmp d/fun -+test ! -f fun+if darcs show files | egrep '^./fun$'; then false; else true; fi++# Now clean up.+cd ..+rm -rf temp+
@@ -0,0 +1,25 @@+#!/usr/bin/env bash+set -ev++rm -rf temp+mkdir temp+cd temp++darcs init+touch fee fi fo fum+darcs add f*+darcs record --author me --all --no-test --patch-name add+darcs mv fee foo+touch fee+darcs add fee+darcs record --author me --all --no-test --patch-name newfee+darcs mv fi fib+darcs record --author me --all --no-test --patch-name mvfi+date > fi+darcs add fi +darcs record --author me --all --no-test --patch-name newfi++cd ..++rm -rf temp+
@@ -0,0 +1,5 @@+#!/usr/bin/env bash+set -ev++# Demonstrates issue385+darcs changes --repo=http://darcs.net GNUmakefile --last 300
@@ -0,0 +1,19 @@+#!/usr/bin/env bash+set -ev++rm -rf temp temp2 temp3++#"$DARCS" get http://darcs.net temp++darcs get --lazy http://darcs.net temp2++darcs get --lazy --tag . http://darcs.net temp3++cd temp2+darcs obliterate --from-tag . -a+darcs pull --tag . -a+cd ..++diff -u temp2/_darcs/hashed_inventory temp3/_darcs/hashed_inventory++rm -rf temp temp2 temp3
@@ -0,0 +1,22 @@+#!/usr/bin/env bash+set -ev++rm -rf temp temp2 temp3++darcs get --lazy http://darcs.net/repos/franchise temp++darcs get --lazy temp temp2++rm -rf temp++cd temp2++test ! -f _darcs/patches/0000003700-fb09ec6a61b35ac7aa77e65388b1a0c182fddca9a908f1a08d0bc5653f2f2d2a++darcs annotate -p 'initial minimal version'++test -f _darcs/patches/0000003700-fb09ec6a61b35ac7aa77e65388b1a0c182fddca9a908f1a08d0bc5653f2f2d2a++cd ..++rm -rf temp temp2 temp3
@@ -0,0 +1,81 @@+#!/usr/bin/env bash+set -ev++# Test the --no-deps option with Send, Push, Pull, Obliterate and Unrecord.+#+# Create four patches with dependencies.+# file 'f': patch 'fa' and 'fb'+# file 'g': patch 'ga' and 'gb'+# The 'b' patches depend on the 'a' patches.++rm -rf tmp1+mkdir tmp1+cd tmp1+darcs init+echo 'record no-ask-deps' >> _darcs/prefs/defaults+echo 'record ignore-times' >> _darcs/prefs/defaults+echo 'a' > f+darcs add f+darcs rec -am 'fa' f+echo 'a' > g+darcs add g+darcs rec -am 'ga' g+echo 'b' > f+darcs rec -am 'fb' f+echo 'b' > g+darcs rec -am 'gb' g+++mkdir d+darcs init --repodir d+++# Try to Send all 'b' and 'g' patches. The two 'g' patches should succeed,+# but the 'fb' patch depends on the unselected 'fa' patch, an should be+# skipped.++darcs send -o bundle -a -p '[bg]' --no-deps d+grep '^\[ga$' bundle+grep '^\[fb$' bundle && exit 1+++# Try to Push all 'b' and 'g' patches. Expect same result as for Send.++darcs push -a -p '[bg]' --no-deps d+cd d+darcs changes | grep '^ \* ga$'+darcs changes | grep '^ \* fb$' && exit 1+# stay in d !!+++# Try to Pull all 'b' and 'g' patches. Expect same result as for Send.++# already in d+rm -rf *; darcs init+darcs pull -a -p '[bg]' --no-deps ..+darcs changes | grep '^ \* ga$'+darcs changes | grep '^ \* fb$' && exit 1+cd ..+++# Try to Obliterate all 'a' and 'g' patches. The two 'g' patches should+# succeed, but the 'fa' patch depends on the unselected 'fb' patch, an+# should be skipped.++darcs get . tmp; cd tmp+echo y/y/y/q | tr / \\012 | darcs obliterate -p '[ag]' --no-deps+darcs changes | grep '^ \* gb$' && exit 1+darcs changes | grep '^ \* fa$'+cd ..+++# Try to Unrecord all 'a' and 'g' patches. Expect same result as for+# Obliterate.++# in "top" tmp repo -- destroys it!+echo y/y/y/q | tr / \\012 | darcs unrecord -p '[ag]' --no-deps+darcs changes | grep '^ \* gb$' && exit 1+darcs changes | grep '^ \* fa$'++cd ..+rm -rf tmp1
@@ -0,0 +1,21 @@+#!/usr/bin/env bash+set -ev++rm -rf temp1 temp2+mkdir temp1 temp2+cd temp1+darcs init+echo -n zig > foo+darcs add foo+sleep 1+darcs record -a -m add_foo -A x+#sleep 1+echo -n zag >> foo+darcs record --ignore-time -a -m mod_foo -A x+cd ../temp2+darcs init+darcs pull -a ../temp1+cd ..+cmp temp1/foo temp2/foo+rm -rf temp1 temp2+
@@ -0,0 +1,19 @@+#!/usr/bin/env bash++. lib++rm -rf temp1+mkdir temp1+cd temp1+darcs init+echo foo > foo+darcs add foo++darcs record -a -m 'addfoo'++darcs obliterate -a++not darcs whatsnew++cd ..+rm -rf temp1
@@ -0,0 +1,32 @@+#!/usr/bin/env bash+# Some tests for 'darcs obliterate'++. lib++rm -rf temp1++# set up the repository+mkdir temp1+cd temp1+darcs init+cd ..++cd temp1+touch a.txt+darcs add a.txt+darcs record -a -m 'adding a' a.txt+touch b.txt+darcs add b.txt+darcs record -a -m 'adding b' b.txt+# extra confirmation for --all+echo an | darcs obliterate -p add | grep -i "really obliterate"+# --last=1+echo n | darcs obliterate --last 1 | grep -i adding+# automatically getting dependencies+date >> a.txt+darcs record -a -m 'modifying a' a.txt+echo n | darcs obliterate -p 'adding a' > log+grep -i "modifying a" log+grep -i "No patches selected" log+cd ..+rm -rf temp1
@@ -0,0 +1,51 @@+#!/usr/bin/env bash+set -ev++rm -rf tempA+mkdir tempA+cd tempA+darcs initialize+echo hello world > foo+darcs add foo+darcs record -a -m hellofoo++echo goodbye world >> foo+darcs record -a -m goodbyefoo++darcs replace world bar foo+echo Hi there foo > bar+darcs add bar+darcs record -a -m addbar++darcs mv bar baz+darcs replace bar baz foo+darcs record -a -m bar2baz++echo Do not love the baz, or anything in the baz. >> foo+darcs record -a -m nolove++darcs mv baz world+darcs replace baz world foo+darcs record -a -m baz2world++darcs whatsnew | grep 'No changes'++grep 'love the world' foo++echo y | darcs obliterate -p baz2world++darcs whatsnew | grep 'No changes'++grep 'love the baz' foo++echo y | darcs obliterate -p bar2baz++grep 'love the bar' foo++echo y | darcs obliterate -p nolove++grep 'love' foo && exit 1 || true++cd ..+rm -rf tempA+
@@ -0,0 +1,16 @@+#!/usr/bin/env bash+set -ev++# tests for "darcs optimize"++rm -rf temp1+mkdir temp1+cd temp1+darcs init+touch foo+darcs add foo+darcs record -a -m add_foo+darcs optimize --reorder-patches| grep -i "done optimizing"+cd ..++rm -rf temp1
@@ -0,0 +1,59 @@++#!/usr/bin/env bash++# For issue600, testing optimize --relink ++set -ev++## We don't support hard links on Windows.++if echo $OS | grep -i windows; then+ echo darcs does not support hard links on Windows+ exit 0+fi++## compare succeeds if there are hard links+compare () {+ echo 'use File::Basename; $res=0; while ($fn=<'$1'/*>) { $fn2="'$2'/" . basename($fn); @fd1=lstat($fn); @fd2=lstat($fn2); $res += ($fd1[1] != $fd2[1]);}; exit($res);' | perl+}++rm -rf temp+mkdir temp+cd temp++mkdir x+darcs init --repodir x+cd x+date > foo+darcs add foo+darcs record -a -A me -m 'addfoo'+cd ..++## Does the filesystem support hard linking at all?+mkdir z1+echo "hi" > z1/foo+mkdir z2+if ! ln z1/foo z2/foo ; then+ echo No ln command for `pwd` - assuming no hard links.+ exit 0+fi+if ! compare z1 z2 ; then+ echo Filesystem for `pwd` does not support hard links.+ exit 0+fi+# workaround for SunOS cp which does not support `-a' option but also+# doesn't fail when it is encountered.+cp -r x y++## Now try relinking using darcs.+rm -rf z+darcs optimize --verbose --relink --repodir x --sibling y+rm -rf x/_darcs/patches/pend* y/_darcs/patches/pend*+if compare x/_darcs/patches y/_darcs/patches+then echo darcs --relink is working, hard links were done.+else echo darcs --relink is not working, it did not make any hard links.+ exit 2+fi++cd ..+rm -rf temp
@@ -0,0 +1,67 @@+#!/usr/bin/env bash+set -ev++rm -rf temp1 temp2 temp3+mkdir temp2+cd temp2+darcs init+cd ..++mkdir temp1+cd temp1+darcs init+touch foo bar+darcs add foo bar+darcs record -a -m foobar -A author+darcs send -a --dont-edit-description -o ../temp3 ../temp2+test -f ../temp3++darcs send -a --dont-edit-description -o ../temp2/patchfile ../temp2+test -f ../temp2/patchfile++mkdir subdir+darcs send -a --dont-edit-description -o subdir/../../temp2/patchfile1 ../temp2+test -f ../temp2/patchfile1++cp ../temp3 correct++cd subdir+darcs send -a --dont-edit-description -o ../patchfile ../../temp2+diff -u ../patchfile ../correct++rm ../patchfile+darcs send -a --dont-edit-description -o - ../../temp2 > ../patchfile+grep -v Creating ../patchfile | diff -u ../correct -++darcs apply --repodir=../../temp2 --dry-run ../patchfile > out+cat out+grep foobar out++cd ../..++cd temp2++darcs apply --dry-run ../temp3 > out+cat out+grep foobar out++darcs apply --dry-run ../temp1/correct > out+cat out+grep foobar out++darcs apply --dry-run ../temp1/patchfile > out+cat out+grep foobar out++darcs apply --dry-run ../temp3 > out+cat out+grep foobar out++darcs apply --dry-run ../temp3 > out+cat out+grep foobar out++cd ..++rm -rf temp1 temp2 temp3+
@@ -0,0 +1,61 @@+#!/usr/bin/env bash++. lib++rm -rf temp+mkdir temp+cd temp+darcs init+darcs setpref test false+darcs record --no-test -a -m 'add failing test'++# should pass with --no-test+darcs check --no-test++# should fail when test is run+not darcs check --test++# should pass with --no-test in defaults+echo check --no-test > _darcs/prefs/defaults+darcs check+not darcs check --test++# should fail with --test in defaults+echo check --test > _darcs/prefs/defaults+not darcs check+darcs check --no-test++# check global defaults+cp ~/.darcs/defaults defaults.backup+trap "cp defaults.backup ~/.darcs/defaults" EXIT+rm _darcs/prefs/defaults++# --no-test works in global defaults+echo check --no-test > ~/.darcs/defaults+darcs check+not darcs check --test++# --test works in global defaults+echo check --test > ~/.darcs/defaults+not darcs check+darcs check --no-test++# Verify that per-repository defaults override global defaults++# --no-test in repository defaults overrides global --test+echo check --test > ~/.darcs/defaults+echo check --no-test > _darcs/prefs/defaults+darcs check+not darcs check --test++# --test in repository defaults overrides global --no-test+echo check --no-test > ~/.darcs/defaults+echo check --test > _darcs/prefs/defaults+not darcs check+darcs check --no-test++trap - EXIT+cp defaults.backup ~/.darcs/defaults++cd ..+rm -rf temp
@@ -0,0 +1,60 @@+#!/usr/bin/env bash++# A partial get of a repo shall have the same _darcs/pristine as the original++set -ev++rm -rf temp+mkdir temp+cd temp++# Create a development repo, do some work++darcs initialize --darcs-2+echo ALL ignore-times >> _darcs/prefs/defaults+touch a+darcs add a+darcs record -a -m aa -A x+touch b+darcs add b+darcs record -a -m bb -A x++# Create a release repo, pull the good patches and tag it++mkdir _rel+cd _rel+darcs initialize --darcs-2 +darcs pull -a --patch a ..+darcs tag -m tt -A x+cd ..++# Pull the tag to the devel repo and continue developement++darcs pull -a _rel+touch c+darcs add c+darcs record -a -m cc -A x++# Create a checkpoint and get a partial temp repo++darcs tag -A x --checkpoint first_checkpoint +darcs get --partial . _partial++# We should have all our devel files++cd _partial+cat a+cat b+cat c++# This is a regression test for issue406+darcs tag -A x --checkpoint checkpointing_a_partial++cd ..++# With the darcs-2 format, doing a get on a partial repo succeeds.+# With the darcs-1 format it would be required to add the --partial flag, or darcs would give an error. +darcs get _partial _second_partial++cd ..+rm -rf temp
@@ -0,0 +1,37 @@+#!/usr/bin/env bash+set -ev++rm -rf temp temp_0+mkdir temp+cd temp+darcs init+date > bla+darcs add bla+darcs record -a --patch-name=11+echo hello > world+darcs add world+darcs whatsnew --dont-look-for-adds > wn1+cd ..+darcs get temp+cd temp_0+date > bla2+date >> bla+darcs add bla2+darcs record -a --patch-name=22+darcs push -a ../temp+cd ..+cd temp+darcs whatsnew --dont-look-for-adds > wn2+diff wn1 wn2++darcs record -a -m 'cleaning up for new test.'+date > foo.jpg+darcs add foo.jpg+darcs whatsnew++darcs remove foo.jpg+darcs whatsnew && exit 1++cd ..++rm -rf temp temp0
@@ -0,0 +1,62 @@+#!/usr/bin/env bash++. lib++rm -rf temp1+mkdir temp1+cd temp1+darcs init++date > date.t+date > date_moved.t++write_buggy_pending () {+cat > _darcs/patches/pending <<EOF+{+addfile ./date.t+addfile ./date_moved.t+move ./date.t ./date_moved.t+}+EOF+}+write_buggy_pending++echo now watch the fireworks as all sorts of things fail+not darcs whatsnew &> out+cat out+grep 'pending has conflicts' out++echo pending should now be fixed but there are no changes+not darcs whatsnew++write_buggy_pending++darcs revert -a &> out+cat out+grep 'pending has conflicts' out++echo pending should now be emptied+darcs revert -a++write_buggy_pending++darcs record -a -m foo &> out+cat out+grep 'pending has conflicts' out++darcs record -a -m foo++darcs changes -v++darcs repair &> out+cat out+grep 'The repository is already consistent' out++write_buggy_pending++darcs repair &> out+cat out+grep 'The repository is already consistent' out++cd ..+rm -rf temp1
@@ -0,0 +1,31 @@+#!/usr/bin/env bash+set -ev++uname | grep "MINGW" > /dev/null && exit 0++rm -rf temp1 temp2+mkdir temp1+cd temp1+darcs init+echo record author me > _darcs/prefs/defaults+echo ALL all >> _darcs/prefs/defaults+echo ALL verbose >> _darcs/prefs/defaults+echo ALL ignore-times >> _darcs/prefs/defaults+touch foo+darcs add foo+darcs record -m add_foo+echo hello >> foo+darcs record -m mod_foo+cd ..+darcs get --repo-name temp2 temp1+cd temp2+cp ../temp1/_darcs/prefs/defaults _darcs/prefs+echo y/d/y | tr / \\012 | darcs unpull --interactive+test -f foo -a ! -s foo+chmod +x foo+test -x foo+darcs pull ../temp1+test -x foo+cd ..+rm -rf temp1 temp2+
@@ -0,0 +1,26 @@+#!/usr/bin/env bash++set -ev++rm -rf temp1+mkdir temp1+cd temp1+darcs init+touch foo+darcs add foo++# Check that posthook works...+darcs whatsnew -s --posthook 'touch posthook-ran'+test -f posthook-ran+rm posthook-ran++# Check that posthook works with defaults...+echo ALL --posthook touch posthook-ran > _darcs/prefs/defaults+darcs whatsnew -s+test -f posthook-ran+rm posthook-ran++echo Successful.++cd ..+rm -rf temp1
@@ -0,0 +1,16 @@+#!/usr/bin/env bash+set -ev++rm -rf temp1 temp2+mkdir temp1+cd temp1+darcs initialize --old-fashion+echo ALL ignore-times >> _darcs/prefs/defaults+cp _darcs/prefs/boring .boring+darcs add .boring+darcs setpref boringfile .boring+darcs record -a -m p1 -A me+cd ..+darcs get --hashed temp1 temp2+cmp temp1/_darcs/prefs/prefs temp2/_darcs/prefs/prefs+rm -rf temp1 temp2
@@ -0,0 +1,23 @@+#!/usr/bin/env bash++# Test that _darcs/prefs/binaries+. lib+rm -rf temp1++mkdir temp1+cd temp1+darcs init+mkdir d+touch d/t.t+darcs add d/t.t+darcs record -am "initial record"+echo 'some change' >> d/t.t+echo n | darcs record --interactive > log+# pre-test: plain text files are not binary+not grep binary log+echo 'd/t' >> _darcs/prefs/binaries+echo n | darcs record --interactive > log+grep binary log+cd ..++rm -rf temp1
@@ -0,0 +1,27 @@+#!/usr/bin/env bash++set -ev++rm -rf temp1+mkdir temp1+cd temp1+darcs init+touch foo+darcs add foo++# Check that prehook runs+darcs whatsnew -s --prehook 'touch prehook-ran'+test -f prehook-ran+rm prehook-ran++# Check that --prehook works with defaults...+echo ALL --prehook touch prehook-ran > _darcs/prefs/defaults+darcs whatsnew -s+test -f prehook-ran+rm prehook-ran+++echo Successful.++cd ..+rm -rf temp1
@@ -0,0 +1,83 @@+#!/usr/bin/env bash++# Some tests for 'darcs printer (the output formating)'++. lib++rm -rf temp1+mkdir temp1+cd temp1+darcs init+touch a+darcs add a+darcs rec -a -m add++env+# clear all output formating environment variables+for e in DARCS_DONT_ESCAPE_ISPRINT DARCS_USE_ISPRINT\+ DARCS_DONT_ESCAPE_8BIT\+ DARCS_DONT_ESCAPE_EXTRA DARCS_ESCAPE_EXTRA\+ DARCS_DONT_ESCAPE_TRAILING_SPACES\+ DARCS_DONT_COLOR DARCS_ALWAYS_COLOR DARCS_ALTERNATIVE_COLOR\+ DARCS_DONT_ESCAPE_ANYTHING; do+ unset $e+done+env++# make sure the locale is c+export LC_ALL=C++test_line () {+ rm -f a+ echo $1 > a+ darcs whatsnew | fgrep $2+}+++# First check escaping and coloring. Use whatsnew, since that is the+# most common use of escapes.++# test the defaults+# - no color to pipe+# - don't escape 7-bit ASCII printables, \n,\t and space (we can't test \n)+# - escape control chars with ^+# - escape other chars with \xXX+test_line " !#%&',-0123456789:;<=>"\+ " !#%&',-0123456789:;<=>"+test_line "ABCDEFGHIJKLMNOPQRSTUVWXYZ_"\+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ_"+test_line "\`abcdefghijklmnopqrstuvwxyz"\+ "\`abcdefghijklmnopqrstuvwxyz"+test_line "\t\"\$()*+./?\@[\\]^{|}"\+ "\t\"\$()*+./?\@[\\]^{|}"+# skip ^@ and ^Z since they make darcs treat the file as binary+# don't put any space control chars at end of line+# ascii control chars are escaped with ^+test_line $(printf '\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0D\x0E')\+ '[_^A_][_^B_][_^C_][_^D_][_^E_][_^F_][_^G_][_^H_][_^K_][_^L_][_^M_][_^N_]'+test_line $(printf '\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19')\+ '[_^O_][_^P_][_^Q_][_^R_][_^S_][_^T_][_^U_][_^V_][_^W_][_^X_][_^Y_]'+test_line $(printf '\x1B') '[_^[_]'+test_line $(printf '\x1C') '[_^\_]'+test_line $(printf '\x1D') '[_^]_]'+test_line $(printf '\x1E') '[_^^_]'+test_line $(printf '\x1F') '[_^__]'+test_line $(printf '\x7F') '[_^?_]'+# other chars are escaped with \xXX+test_line $(printf '\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8A\x8B\x8C\x8D\x8E\x8F')\+ '[_\80_][_\81_][_\82_][_\83_][_\84_][_\85_][_\86_][_\87_][_\88_][_\89_][_\8a_][_\8b_][_\8c_][_\8d_][_\8e_][_\8f_]'+test_line $(printf '\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9A\x9B\x9C\x9D\x9E\x9F')\+ '[_\90_][_\91_][_\92_][_\93_][_\94_][_\95_][_\96_][_\97_][_\98_][_\99_][_\9a_][_\9b_][_\9c_][_\9d_][_\9e_][_\9f_]'+test_line $(printf '\xA0\xA1\xA2\xA3\xA4\xA5\xA6\xA7\xA8\xA9\xAA\xAB\xAC\xAD\xAE\xAF')\+ '[_\a0_][_\a1_][_\a2_][_\a3_][_\a4_][_\a5_][_\a6_][_\a7_][_\a8_][_\a9_][_\aa_][_\ab_][_\ac_][_\ad_][_\ae_][_\af_]'+test_line $(printf '\xB0\xB1\xB2\xB3\xB4\xB5\xB6\xB7\xB8\xB9\xBA\xBB\xBC\xBD\xBE\xBF')\+ '[_\b0_][_\b1_][_\b2_][_\b3_][_\b4_][_\b5_][_\b6_][_\b7_][_\b8_][_\b9_][_\ba_][_\bb_][_\bc_][_\bd_][_\be_][_\bf_]'+test_line $(printf '\xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF')\+ '[_\c0_][_\c1_][_\c2_][_\c3_][_\c4_][_\c5_][_\c6_][_\c7_][_\c8_][_\c9_][_\ca_][_\cb_][_\cc_][_\cd_][_\ce_][_\cf_]'+test_line $(printf '\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD7\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF')\+ '[_\d0_][_\d1_][_\d2_][_\d3_][_\d4_][_\d5_][_\d6_][_\d7_][_\d8_][_\d9_][_\da_][_\db_][_\dc_][_\dd_][_\de_][_\df_]'+test_line $(printf '\xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF')\+ '[_\e0_][_\e1_][_\e2_][_\e3_][_\e4_][_\e5_][_\e6_][_\e7_][_\e8_][_\e9_][_\ea_][_\eb_][_\ec_][_\ed_][_\ee_][_\ef_]'+test_line $(printf '\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF7\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xFF')\+ '[_\f0_][_\f1_][_\f2_][_\f3_][_\f4_][_\f5_][_\f6_][_\f7_][_\f8_][_\f9_][_\fa_][_\fb_][_\fc_][_\fd_][_\fe_][_\ff_]'+rm -rf temp1
@@ -0,0 +1,31 @@+#!/usr/bin/env bash++set -ev++# Check that the right patches get pulled using --dont-prompt-for-dependencies++rm -rf temp1+rm -rf temp2+mkdir temp2+mkdir temp1+cd temp2+darcs init+cd ..+cd temp1+darcs init+echo foo > f+darcs record -Ax -alm foo1+echo bar > b+darcs rec -Ax -alm bar1+echo foo2 > f+darcs record -Ax -alm foo2+echo bar2 > b+darcs record -Ax -alm bar2+cd ../temp2+echo y | darcs pull ../temp1 --dont-prompt-for-dependencies -p foo2 --dry-run > toto+#on the previous line, we don't get asked about foo1.+grep foo toto | wc -l | grep 2+#but we send it anyway.+grep bar toto | wc -l | grep 0+cd ..+rm -rf temp1 temp2
@@ -0,0 +1,42 @@+#!/usr/bin/env bash++. lib++# This test script is in the public domain.+++rm -rf temp1 temp2 temp3++mkdir temp1+cd temp1+darcs initialize+echo A > A+darcs add A+darcs record -a -m A+echo B > B+darcs add B+darcs record -a -m B++cd ..+darcs get temp1 temp2++cd temp2+darcs obliterate --last 1 -a+echo C > C+darcs add C+darcs record -a -m C+cd ..++mkdir temp3+cd temp3+darcs init+darcs pull -a -v ../temp1 ../temp2++darcs changes > out+cat out+grep A out+grep B out+grep C out+cd ..++rm -rf temp1 temp2 temp3
@@ -0,0 +1,154 @@+#!/usr/bin/env bash++. lib++rm -rf temp1 temp2++mkdir temp1+cd temp1+darcs init++cd ..+mkdir temp2+cd temp2+darcs init++mkdir one+cd one+mkdir two+cd two+echo darcs pull should work relative to the current directory+darcs pull -a ../../../temp1 | grep -i 'No remote changes to pull in'++echo -- darcs pull should pull into repo specified with --repo+cd ../.. # now in temp2+darcs add one;+darcs record --patch-name uno --all+cd .. # now outside of any repo+darcs pull --repodir temp1 --all temp2 | grep -i 'Finished pulling.' # temp2 is not relative to temp1++# set up server repo+date > temp2/one/date.t+darcs add --repodir ./temp2 one/date.t+darcs record --repodir ./temp2 -a -m foo++# set up client repo for failure+if echo $OS | grep -i windows; then+ echo this test does not work on windows because it+ echo is not possible to chmod -r+elif whoami | grep root; then+ echo root never gets permission denied+else+ chmod a-rwx ./temp1/one # remove all permissions+ not darcs pull --repodir ./temp1 -a 2> err+ chmod u+rwx temp1/one # restore permission+ cat err+ grep 'permission denied' err+ rm -rf temp1/one+fi++cd temp1++echo Before trying to pull from self, defaultrepo is something else+not grep temp1 _darcs/prefs/defaultrepo++#return special message when you try to pull from yourself+DIR="`pwd`"+not darcs pull --debug -a "$DIR" 2> out+cat out+grep 'Can.t pull from current repository' out++not darcs pull --debug -a . 2> out+cat out+grep 'Can.t pull from current repository' out++# and do not update the default repo to be the current di+not grep temp1 _darcs/prefs/defaultrepo++rm -f _darcs/prefs/defaultrepo+not darcs pull 2> err+grep 'please specify one' err+echo . > _darcs/prefs/defaultrepo+not darcs pull --debug 2> err+grep 'Can.t pull from current repository' err++not darcs pull --debug ../* 2> out+cat out+not grep 'Can.t pull from current repository' out+cd .. # now outside of any repo++cd temp1+echo a > foo+darcs record -lam AA+echo b > foo+darcs record -lam BB+echo c > foo+darcs record -lam CC+darcs rollback -p CC -a -m unC+cd ..+rm -rf temp2+darcs get --to-patch B temp1 temp2+cd temp2+sleep 1 # So that rollback won't have same timestamp as get.+darcs rollback -p BB -a -m unB+darcs revert -a+darcs pull -a ../temp1 2> err2+not grep 'Error applying patch' err2+cd ..++cd temp1+echo -n foo > baz+darcs add baz+darcs record -am newbaz+cd ../temp2+darcs pull -a | grep Finished+echo -n bar > baz+darcs record -am bazbar+cd ../temp1+darcs pull ../temp2 -a+echo -n bar > correct_baz+diff baz correct_baz+cd ..++# my $test_name = "when a patch creating a directory is attempted to be applied+# while a directory with that name already exists, a warning is raised, but+# the pull succeeds.";+mkdir temp1/newdir+cd temp1+darcs add newdir+darcs record -am newdir+cd ../temp2+mkdir newdir+darcs pull -a ../temp1 &> out2+cat out+grep Backing out2+grep 'Finished pulling' out2+grep newdir out2+cd ..++rm -rf temp1 temp2+++# A test for issue662, which triggered:+# darcs failed: Error applying hunk to file ./t.t+# Error applying patch to the working directory.++rm -rf tmp;+darcs init --hashed --repodir=tmp+touch tmp/t.t+cd tmp+darcs add t.t+echo 'content'>t.t+darcs record -am 'initial add' --ignore+echo 'content: remote change'>t.t+darcs record -am 'remote change' --ignore+darcs put tmp2+cd tmp2+darcs obliterate --last 1 --all;+echo 'content: local change'> t.t+darcs pull -a ../+darcs w -s+darcs revert -a++cd ../..+rm -rf tmp
@@ -0,0 +1,27 @@+#!/usr/bin/env bash++# This test script, originally written by David Roundy and Ian Lynagh is in+# the public domain.++set -ev++rm -rf temp1 temp2++mkdir temp1+cd temp1+darcs init+printf "%01048576d" 0 > foo+darcs record -l -a -A author -m xx+rm foo+darcs record -a -A author -m yy+cd ..++mkdir temp2+cd temp2+darcs init+echo yn | darcs pull ../temp1+rm foo+darcs pull -a+cd ..++rm -rf temp1 temp2
@@ -0,0 +1,121 @@+#!/usr/bin/env bash++# This test script is in the public domain.++set -ev++rm -rf temp1 temp2 temp3 temp4 temp5++mkdir temp1+cd temp1+cat > foo <<EOF+line1+line2+line3+line4+line5+line6+line7+EOF+darcs initialize+darcs add foo+darcs record -a -m addfoo++cd ..+darcs get temp1 temp4++chgrec () {+ set -ev+ sed -e "$1" foo > foo.tmp+ mv foo.tmp foo+ darcs record -a --ignore-times -m "$2"+}++cd temp1+chgrec 's/line2/line2\nline2.1\nline2.2/' inssub2+chgrec 's/line4/Line 4/' Line4++darcs changes | grep ' \*'+echo done with changes on temp1 > /dev/null++cd ..+darcs get temp1 temp2+darcs get temp1 temp3+cd temp1++chgrec 's/line1/line0\nline1/' line0+chgrec 's/Line 4/LINE FOUR/' LINE4+chgrec 's/line7/line7\nLastLine/' LastLine+chgrec 's/LINE FOUR/LINE FOUR\nline4.1/' line4.1++darcs changes | grep ' \*'+echo done with changes on temp1 > /dev/null++cd ../temp3+darcs pull -p LastLine -av+chgrec 's/line1$/FirstLine/' FirstLine++cd ../temp4++darcs changes | grep ' \*'+echo done with changes on temp4 > /dev/null++darcs pull ../temp1 --dry-run | grep ' \*'+darcs pull ../temp1 --dry-run | grep ' \*' > p1.out+cat > p1.req <<EOF+ * inssub2+ * Line4+ * line0+ * LINE4+ * LastLine+ * line4.1+EOF+diff p1.req p1.out++darcs pull ../temp1 --dry-run --complement | grep ' \*' > p2.out+diff p1.out p2.out++darcs pull --dry-run --complement ../temp1 ../temp2 | grep ' \*' > p3.out+cat > p3.req <<EOF+ * line0+ * LastLine+EOF+diff p3.req p3.out++darcs pull --dry-run --complement ../temp1 ../temp3 | grep ' \*' > p4.out+cat > p4.req <<EOF+ * line0+EOF+diff p3.req p3.out++darcs pull --dry-run --complement ../temp1 ../temp2 ../temp3 | grep ' \*' > p5.out+diff p4.out p5.out++darcs pull --dry-run --complement ../temp1 ../temp2 ../temp3 ../temp2 ../temp2 ../temp3 ../temp3 ../temp2 | grep ' \*' > p6.out+diff p4.out p6.out++darcs pull --dry-run --complement ../temp3 ../temp2 | grep ' \*' > p7.out+cat > p7.req <<EOF+ * LastLine+EOF++darcs pull --dry-run --complement ../temp2 ../temp3 > p8.out+grep "No remote changes to pull in!" p8.out++# because duplicates are stripped before performing action,+# this is the same as: darcs pull ../temp1+darcs pull --dry-run --complement ../temp1 ../temp1 > fooout+cat fooout+grep ' \*' fooout > p9.out+diff p1.req p9.out++# so the "null" pull must be tested this way:+darcs get ../temp1 ../temp5+darcs pull --dry-run --complement ../temp1 ../temp5 > p9.out+grep "No remote changes to pull in!" p9.out++darcs pull -av --complement ../temp1 ../temp3+darcs check++cd ..+rm -rf temp1 temp2 temp3 temp4 temp5
@@ -0,0 +1,27 @@+#!/usr/bin/env bash+set -ev++rm -rf temp1 temp2++if grep old-fashioned .darcs/defaults; then+format=old-fashioned-inventory+elif grep darcs-2 .darcs/defaults; then+format=darcs-2+else format=hashed; fi++mkdir temp2+cd temp2+gunzip -c ../repos/many-files--${format}.tgz | tar xf -+cd ..++mkdir temp1+cd temp1+darcs init+darcs pull -a ../temp2/many-files--${format} > log+grep -i 'finished pulling' log+cd ..+rm -rf temp1++# put things back how we found them.++rm -rf temp1 temp2
@@ -0,0 +1,34 @@+#!/usr/bin/env bash++# This test script, originally written by David Roundy is in the public+# domain.++set -ev++rm -rf temp1 temp2++mkdir temp1+cd temp1+echo foo > bar+darcs initialize+echo record author me > _darcs/prefs/defaults+darcs add bar+darcs record -a -m addbar++cd ..+darcs get temp1 temp2+cd temp1+date > bar+darcs record -a -m datebar++cd ../temp1+echo aack >> bar+darcs record -a -m aackbar++cd ../temp2++darcs pull -av+darcs check++cd ..+rm -rf temp1 temp2
@@ -0,0 +1,30 @@+#!/usr/bin/env bash++set -ev++# Check that the right patches get pushed using --dont-prompt-for-dependencies++rm -rf temp1+rm -rf temp2+mkdir temp2+mkdir temp1+cd temp2+darcs init+cd ..+cd temp1+darcs init+echo foo > f+darcs record -Ax -alm foo1+echo bar > b+darcs rec -Ax -alm bar1+echo foo2 > f+darcs record -Ax -alm foo2+echo bar2 > b+darcs record -Ax -alm bar2+echo y | darcs push ../temp2 --dont-prompt-for-dependencies -p foo2 --dry-run > toto+#on the previous line, we don't get asked about foo1.+grep foo toto | wc -l | grep 2+#but we send it anyway.+grep bar toto | wc -l | grep 0+cd ..+rm -rf temp1 temp2
@@ -0,0 +1,28 @@+#!/usr/bin/env bash+set -ev++# For issue855: wish: avoid taking lock if using --dry-run+chmod -R u+w temp2 || :+rm -rf temp1 temp2+mkdir temp1+cd temp1+darcs init+cd ..+mkdir temp2+cd temp2+darcs init+touch x+darcs add x+darcs record -am "test"+cd ..+chmod -R u-w temp2+cd temp2+# need to capture this failure so that we can still+# chmod -R u+w the directory even if we fail+darcsexit=0+darcs push --dry-run ../temp1 || darcsexit=$?+cd ..+chmod -R u+w temp2 # so that other scripts can cleanup+if [ $darcsexit -ne 0 ]; then+ exit $darcsexit+fi
@@ -0,0 +1,49 @@+#!/usr/bin/env bash++# Some tests for 'darcs push'++. lib++DIR="`pwd`"++rm -rf temp1 temp2+mkdir temp1+cd temp1+darcs init+cd ..+mkdir temp2+cd temp2+darcs init+cd ..++# push without a repo gives an error+cd temp1+not darcs push -p 123 2> log+grep -i 'missing argument' log+cd ..++mkdir -p temp2/one/two+cd temp2/one/two+# darcs push should work relative to the current directory+darcs push -a ../../../temp1 | grep -i 'No recorded local changes to push'+cd ../../../++# darcs push should push into repo specified with --repo+cd temp2+darcs add one+darcs record --patch-name uno --all+cd ..++darcs push --repodir temp2 --all temp1 | grep -i 'Finished apply'++cd temp1+# Before trying to pull from self, defaultrepo does not exist+test ! -e _darcs/prefs/defaultrepo+# return special message when you try to push to yourself+not darcs push -a "$DIR/temp1" 2> log+grep -i "cannot push from repository to itself" log+# and don't update the default repo to be the current dir+test ! -e _darcs/prefs/defaultrepo+cd ..++rm -rf temp1 temp2
@@ -0,0 +1,21 @@+#!/usr/bin/env bash+set -ev++rm -rf temp temp_0+mkdir temp+cd temp+darcs init+echo tester > _darcs/prefs/author+date > bla+darcs add bla+darcs record -a --patch-name=11+cd ..+darcs get temp+cd temp+date > bla2+darcs add bla2+darcs record -a --patch-name=22+darcs push -a ../temp_0+cd ..++rm -rf temp temp_0
@@ -0,0 +1,26 @@+#!/usr/bin/env bash++# For issue257: push => incorrect return code when couldn't get lock++set -ev++rm -rf tempc+mkdir tempc+cd tempc+darcs init+echo foo > foo.c+darcs rec -Ax -alm init+cd ..+rm -rf temps+darcs get tempc temps+cd temps+echo server >> foo.c+darcs rec -Ax -alm server+cd ../tempc+echo client >> foo.c+darcs rec -Ax -alm client+if darcs push -a ../temps; then+ false+fi+cd ..+rm -rf tempc temps
@@ -0,0 +1,31 @@+#!/usr/bin/env bash++. lib++rm -rf temp1 temp2+mkdir temp1+cd temp1+darcs init+cd ..++# put should set default repo+cd temp1+touch 1.txt+darcs add 1.txt+darcs record -a -m foo 1.txt+darcs put ../temp2+test -e _darcs/prefs/defaultrepo+grep temp2 _darcs/prefs/defaultrepo+cd ..++# put to self+cd temp1+not grep temp1 _darcs/prefs/defaultrepo+DIR=`pwd`+# return special message when you try to put put yourself+not darcs put "$DIR" 2> log+not grep temp1 _darcs/prefs/defaultrepo+grep -i "Can't put.*current repository" log+cd ..++rm -rf temp1 temp2
@@ -0,0 +1,90 @@+#!/usr/bin/env bash+set -ev++check_manifest () {+ : > files.tmp+ echo . > dirs.tmp+ echo . > files-dirs.tmp+ for x in $1 ; do+ echo "./$x" >> files.tmp+ echo "./$x" >> files-dirs.tmp+ done+ for x in $2 ; do+ echo "./$x" >> dirs.tmp+ echo "./$x" >> files-dirs.tmp+ done+ darcs query manifest $3 --files --no-directories | sort > darcs-files.tmp+ darcs query manifest $3 --no-files --directories | sort > darcs-dirs.tmp+ darcs query manifest $3 --files --directories | sort > darcs-files-dirs.tmp+ for x in files dirs files-dirs ; do+ sort $x.tmp >sorted-$x.tmp+ diff sorted-$x.tmp darcs-$x.tmp+ done }++rm -rf temp+mkdir temp+cd temp+darcs init++check_manifest "" "" "--no-pending"+check_manifest "" "" "--pending"+touch a b+darcs add a+check_manifest "" "" "--no-pending"+check_manifest "a" "" "--pending"+darcs add b+mkdir c+check_manifest "" "" "--no-pending"+check_manifest "a b" "" "--pending"+darcs add c+touch c/1 c/2+check_manifest "" "" "--no-pending"+check_manifest "a b" "c" "--pending"+darcs add c/1 c/2+check_manifest "" "" "--no-pending"+check_manifest "a b c/1 c/2" "c" "--pending"+mkdir d+touch d/3 d/4+darcs add d/3 d/4+check_manifest "" "" "--no-pending"+check_manifest "a b c/1 c/2 d/3 d/4" "c d" "--pending"+darcs record -A test --all --patch-name "patch 1" --skip-long-comment+check_manifest "a b c/1 c/2 d/3 d/4" "c d" "--no-pending"+check_manifest "a b c/1 c/2 d/3 d/4" "c d" "--pending"++darcs mv d e+check_manifest "a b c/1 c/2 d/3 d/4" "c d" "--no-pending"+check_manifest "a b c/1 c/2 e/3 e/4" "c e" "--pending"+rm c/1+check_manifest "a b c/1 c/2 d/3 d/4" "c d" "--no-pending"+check_manifest "a b c/1 c/2 e/3 e/4" "c e" "--pending"+darcs remove c/1+check_manifest "a b c/1 c/2 d/3 d/4" "c d" "--no-pending"+check_manifest "a b c/2 e/3 e/4" "c e" "--pending"+darcs mv c/2 c/1+check_manifest "a b c/1 c/2 d/3 d/4" "c d" "--no-pending"+check_manifest "a b c/1 e/3 e/4" "c e" "--pending"+darcs record -A test --all --patch-name "patch 2" --skip-long-comment+check_manifest "a b c/1 e/3 e/4" "c e" "--no-pending"+check_manifest "a b c/1 e/3 e/4" "c e" "--pending"++darcs remove c/1+check_manifest "a b c/1 e/3 e/4" "c e" "--no-pending"+check_manifest "a b e/3 e/4" "c e" "--pending"+darcs remove c+check_manifest "a b c/1 e/3 e/4" "c e" "--no-pending"+check_manifest "a b e/3 e/4" "e" "--pending"+darcs record -A test --all --patch-name "patch 3" --skip-long-comment+check_manifest "a b e/3 e/4" "e" "--no-pending"+check_manifest "a b e/3 e/4" "e" "--pending"++darcs mv b b2+darcs mv b2 b3+check_manifest "a b e/3 e/4" "e" "--no-pending"+check_manifest "a b3 e/3 e/4" "e" "--pending"+darcs record -A test --all --patch-name "patch 3" --skip-long-comment+check_manifest "a b3 e/3 e/4" "e" "--no-pending"+check_manifest "a b3 e/3 e/4" "e" "--pending"++cd ..+rm -rf temp
@@ -0,0 +1,27 @@+#!/usr/bin/env bash++. lib+++rm -rf temp1+mkdir temp1+cd temp1+darcs init++touch foo+darcs add foo+darcs record -a -m addfoo++darcs replace one two foo+darcs replace three four foo+darcs replace five six foo++echo sa | darcs record -m cancelled++darcs whatsnew++darcs changes > ch+not grep cancelled ch++cd ..+rm -rf temp1
@@ -0,0 +1,53 @@+#!/usr/bin/env bash++# Some tests for 'darcs record '++. lib++rm -rf temp1+mkdir temp1+cd temp1+darcs init++# issue308 - no patches and no deps for record should abort+darcs record -am foo --ask-deps | grep -i "Ok, if you don't want to record anything, that's fine!"++# RT#476 - --ask-deps works when there are no patches+if echo $OS | grep -i windows; then+ echo This test does not work on Windows+else+ touch t.f+ darcs add t.f+ darcs record -am add+ echo a | darcs record -am foo --ask-deps | grep -i 'finished recording'+fi++# RT#231 - special message is given for nonexistent directories+not darcs record -am foo not_there.txt > log+grep -i 'non existent' log++# RT#231 - a nonexistent file before an existing file is handled correctly+touch b.t+darcs record -am foo a.t b.t > log+grep -i 'non existent files or directories: "a.t"' log++DIR="`pwd`"+touch date.t+darcs add date.t+darcs record -a -m foo "$DIR/date.t" | grep -i 'finished recording'++# issue396 - record -l ""+touch 'notnull.t'+darcs record -am foo -l "" notnull.t | grep -i 'finished recording'++# basic record+date >> date.t+darcs record -a -m basic_record date.t | grep -i 'finished recording'++# testing --logfile+date >> date.t+echo "second record\n" >> log.txt+darcs record -a -m 'second record' --logfile=log.txt date.t | grep -i 'finished recording'++cd ..+rm -rf temp1
@@ -0,0 +1,59 @@+#!/usr/bin/env bash++# Some tests for 'darcs rec --edit-long-comment'++. lib++abort_windows++rm -rf temp1++export DARCS_EDITOR="/bin/cat -n"+# editor: space in command+mkdir temp1+cd temp1+darcs init+touch file.t+darcs add file.t+echo y | darcs record --edit-long-comment -a -m foo file.t | grep '2.*END OF DESCRIPTION'+cd ..+rm -rf temp1++# editor: space in path+mkdir temp2\ dir+cd temp2\ dir+darcs init+touch file.t+darcs add file.t+echo y | darcs record --edit-long-comment -a -m foo file.t | grep '2.*END OF DESCRIPTION'+cd ..+rm -rf temp2\ dir++export DARCS_EDITOR='grep "END OF"'+# editor: quoting in command+mkdir temp1+cd temp1+darcs init+touch file.t+darcs add file.t+echo y | darcs record --edit-long-comment -a -m foo file.t | grep 'END OF'+cd ..+rm -rf temp1++export DARCS_EDITOR='/bin/echo'+# editor: evil filename+mkdir temp1+cd temp1+darcs init+touch file.t+darcs add file.t+touch '; test-command'+echo > test-command << FOO+#!/bin/sh+echo EVIL+FOO+chmod u+x test-command+echo y | darcs record --logfile='; test-command' --edit-long-comment -a -m foo file.t > log+not grep EVIL log+cd ..+rm -rf temp1
@@ -0,0 +1,21 @@+#!/usr/bin/env bash+set -ev++rm -rf foo1 foo2+mkdir foo1 foo2+cd foo1+darcs init+echo zig > foo+darcs add foo+sleep 1+darcs record -a -m add_foo -A x+#sleep 1+echo zag >> foo+darcs record --ignore-time -a -m mod_foo -A x+cd ../foo2+darcs init+darcs pull -a ../foo1+cd ..+cmp foo1/foo foo2/foo+rm -rf foo1 foo2+
@@ -0,0 +1,25 @@+#!/usr/bin/env bash+set -ev++rm -rf temp1+mkdir temp1+cd temp1++darcs init+touch baz+darcs add baz+darcs record -m moo -a++cat _darcs/patches/pending++darcs changes -v++darcs check++# check that repair doesn't do anything to a clean repository+darcs repair > out+cat out+grep 'already consistent' out++cd ..+rm -rf temp1
@@ -0,0 +1,53 @@+. lib++rm -rf bad+mkdir bad+cd bad+darcs init++echo foo > bar+darcs add bar+darcs rec -a -m 'foo'++echo hey > foo+darcs add foo+darcs rec -a -m 'more foo'++hashed=false+test -e _darcs/hashed_inventory && hashed=true++# produce a corrupt patch+echo 'rmfile foo' > _darcs/patches/pending+$hashed || echo -n > _darcs/pristine/foo+darcs rec -a -m 'remove foo'++not darcs check # unapplicable patch!++# stash away contents of _darcs+cp -R _darcs/ _backup_darcs++echo here > bar+darcs rec -a -m 'here'++# corrupt pristine content+$hashed && inv=`grep ^pristine _darcs/hashed_inventory`+cp _backup_darcs/patches/* _darcs/patches/+cp _backup_darcs/*inventory* _darcs/+$hashed && {+ cp _darcs/hashed_inventory hashed.tmp+ sed -e "s,^pristine:.*$,$inv," < hashed.tmp > _darcs/hashed_inventory+ rm hashed.tmp+}++not darcs check # just a little paranoia++darcs repair # repair succeeds+darcs check # and the resulting repo is consistent++# *AND* it contains what we expect...+darcs show contents bar > foobar+echo foo > foobar1+diff foobar foobar1++cd ..+rm -rf bad
@@ -0,0 +1,30 @@+#!/usr/bin/env bash+set -ev++rm -rf temp1+mkdir temp1+cd temp1+darcs initialize+echo ALL ignore-times >> _darcs/prefs/defaults+echo A1 > foo+mkdir d+echo A2 > d/bar+darcs add foo+darcs add d+darcs add d/bar+darcs record -a -m AA -A x+echo B > foo+darcs record -a -m BB -A x+echo C > foo+darcs record -a -m CC -A x++for i in _darcs/pristine*; do+ echo Empty the pristine directory: $i+ rm -rf $i+ mkdir $i+done++darcs repair++cd ..+rm -rf temp1
@@ -0,0 +1,61 @@+#!/usr/bin/env bash+set -ev++rm -rf temp+mkdir temp+cd temp++darcs init++echo "X X X" > foo+echo $'A A,A\tA,A\vA' >> foo+darcs rec -alm "Added"++# These should fail until replace handles tokens and+# token-chars with leteral spaces in them+darcs replace ' X ' ' XX ' --token-chars '[ X]' foo && exit 1 || true+darcs replace $'A A' 'aaa' --token-chars '[^,]' foo && exit 1 || true+darcs replace $'A\tA' 'aaa' --token-chars '[^,]' foo && exit 1 || true+darcs replace $'A\vA' 'aaa' --token-chars '[^,]' foo && exit 1 || true++# Check that replace is not fooled by duplicate file names+# (i.e. not trying to performe the replace twice in the same file)+darcs replace X Y foo foo+darcs replace Y Z foo ../temp/foo+darcs replace Z Q foo foo --repodir=../temp/+darcs rec -am "xyzq"+++# Try to "overwrite" a hunk with a replace.+#+# v1.0.8 accepts this without error or warning,+# but should perhaps require the --force option?+#+# current unstable sometimes(!) fails with bug: invalid pending+# which is surely a bug.++# this succeeds+echo "x" > foo+darcs rec -am xx+echo "y" > foo+darcs replace --ignore-times x y foo++# this fails+echo "hej" > foo+darcs rec -am hej+echo "hopp" > foo+darcs replace hej hopp foo++darcs whatsnew++echo "src" > foo+echo "dst" >> foo+darcs rec -am hop+darcs replace src dst foo || true+darcs replace --force src dst foo++darcs whatsnew+darcs whatsnew -ls++cd ..+rm -rf temp
@@ -0,0 +1,21 @@+#!/usr/bin/env bash++set -ev++rm -fr temp1++mkdir temp1+cd temp1+darcs --version++darcs init++echo a b a b a b > A+darcs add A+if darcs replace a c A | grep Skipping; then+ exit 1+fi+cd ..++rm -fr temp1+
@@ -0,0 +1,21 @@+#!/usr/bin/env bash++set -ev++rm -fr temp1++mkdir temp1+cd temp1+darcs init++echo a b a b a b > A+darcs add A+darcs record --all --patch-name=p1+darcs mv A B+if darcs replace a c B | grep Skipping; then+ exit 1+fi+cd ..++rm -fr temp1+
@@ -0,0 +1,28 @@+#!/usr/bin/env bash+### http://bugs.darcs.net/issue496+### _darcs/prefs/defaults ignored when using --repodir++## All these commands SHOULD fail (hence leading NOTs).+. lib++rm -rf temp+mkdir temp+cd temp++mkdir repo+darcs initialize --repodir repo+cd repo+date > foo+darcs add foo+darcs record -a -m auth++echo > _darcs/prefs/defaults ALL disable # try to disable all+not darcs changes+not darcs changes --repodir "`pwd`"++cd ..++not darcs changes --repodir repo+not darcs changes --repodir "`pwd`/repo"++rm -rf temp
@@ -0,0 +1,14 @@+#!/usr/bin/env bash++. lib++rm -rf temp1 temp2+mkdir temp1+cd temp1+darcs init+echo gobbledygook > _darcs/format+cd ..++not darcs get temp1 temp2 2> log+grep -i "can't understand repository format" log+rm -rf temp1 temp2 log
@@ -0,0 +1,4 @@++Larger or more complex pre-built repos are stored here for testing. They are+stored as archives for space efficiency, and to emphasize that they are meant+to be read-only.
binary file changed (absent → 250790 bytes)
binary file changed (absent → 169112 bytes)
binary file changed (absent → 87207 bytes)
binary file changed (absent → 114197 bytes)
@@ -0,0 +1,67 @@+#!/usr/bin/env bash+set -ev++rm -rf temp1+mkdir temp1+cd temp1+darcs init+echo hello world > foo+darcs add foo+darcs record -a -m add -A x+echo goodbye world >> foo+echo y/y | tr / \\012 | darcs revert+darcs show contents foo | cmp foo -++# Now let's test a trickier revert where changes commute nontrivially.++cat > foo <<EOF+a+b+c+d+e+EOF++darcs record -a -A me -m cleanup++mv foo foo.tmp+cat foo.tmp | grep -v b | grep -v d > foo++echo "nyy" | darcs revert++DARCS_DONT_COLOR=1 darcs wh > whatsnew+cat > correct <<EOF+hunk ./foo 2+-b+EOF+diff -c correct whatsnew++# Try a situation where earlier (kept) changes are depended upon by the+# changes we want to revert:++darcs record -a -A me -m cleanup++echo hello world > bar+echo hello world > foo+darcs add bar+darcs replace hello goodbye bar foo++echo "cnnnyy/y" | tr / \\012 | darcs revert++DARCS_DONT_COLOR=1 darcs wh > whatsnew+cat > correct <<EOF+addfile ./bar+hunk ./bar 1++goodbye world+hunk ./foo 1+-a+-c+-d+-e++hello world+EOF+diff -c correct whatsnew++cd ..+rm -rf temp1+
@@ -0,0 +1,13 @@+#!/usr/bin/env bash+set -ev++rm -rf temp+mkdir temp+cd temp++darcs init++echo stuff > foo+darcs add foo++darcs revert -a
@@ -0,0 +1,24 @@+#!/usr/bin/env bash+set -ev++rm -rf tempA tempB+mkdir tempA tempB+cd tempA+darcs init+touch foo+darcs add foo+darcs record -a -m addA -A x+cd ../tempB+darcs init+darcs pull -a ../tempA+darcs remove foo+# rm foo+darcs record -a -m rmB -A x+cd ../tempA+darcs remove foo+darcs record -a -m rmA -A x+cd ../tempB+darcs pull -a ../tempA+cd ..+rm -rf tempA tempB+
@@ -0,0 +1,32 @@+#!/usr/bin/env bash++. lib++rm -rf temp1+mkdir temp1+cd temp1+darcs init+mkdir foo+echo hello world > foo/bar+echo hello world > foo/baz+mkdir foo/dir+darcs add foo foo/bar foo/dir foo/baz+darcs record -a -m add+rm -rf foo+darcs show files --no-pending --no-dir > log+grep 'foo/baz' log+grep 'foo/bar' log+darcs show files --no-pending --no-fil > log+grep 'foo/dir' log+grep 'foo$' log+# now without...+darcs record -a -m del+darcs show files --no-pending --no-dir > log+not grep 'foo/baz' log+not grep 'foo/bar' log+darcs show files --no-pending --no-fil > log+not grep 'foo/dir' log+not grep 'foo$' log+cd ..++rm -rf temp1
@@ -0,0 +1,71 @@+#!/usr/bin/env bash+set -ev++rm -rf temp1 temp2 temp3++# initialise temp1+mkdir temp1+cd temp1+darcs initialize+mkdir foo+echo hello world > foo/bar+darcs add foo foo/bar+darcs record -a -m add+cd ..++# get temp1 into temp2+darcs get temp1 temp2+cd temp2+echo hello world > foo/baz+cd ..++# remove a directory from temp1 and record+cd temp1+rm -rf foo+darcs record -a -m del+cd ..++cd temp2+test -e foo/baz+test -e foo/bar+test -d foo++darcs show files --no-pending --no-dir >> files+grep foo/bar files+darcs show files --no-pending --no-fil >> dirs+grep foo dirs+cd ..++darcs pull -a --repodir=temp2 > pullresult+cat pullresult+grep 'Warning: .ot deleting' pullresult++# get temp1 into temp3+darcs get temp1 temp3+cd temp3+darcs obliterate --last 1 -a+echo hello world > foo/baz+cd ..++# remove a directory from temp1 and record+cd temp1+rm -rf foo+darcs record -a -m del+cd ..++cd temp3+test -e foo/baz+test -e foo/bar+test -d foo++darcs show files --no-pending --no-dir >> files+grep foo/bar files+darcs show files --no-pending --no-fil >> dirs+grep foo dirs+cd ..++darcs pull -q -a --repodir=temp3 > pullresult+cat pullresult+test ! -s pullresult++rm -rf temp1 temp2 temp3
@@ -0,0 +1,16 @@+#!/usr/bin/env bash++set -ev++rm -rf temp+mkdir temp+cd temp+darcs init+date > file1+darcs add file1+darcs record -am "test"+rm file1+darcs record -am "rm"+echo yd | darcs rollback --last=1 -m 'oops' | grep 'No changes selected'+cd ..+rm -rf temp
@@ -0,0 +1,34 @@+#!/bin/bash++set -ev++range="0 1 2"++rm -rf temp1 temp2 # clean up when previous run crashed+mkdir temp1 && cd temp1 && darcs init+touch f && darcs add f+for i in $range+do+ echo $i > f && darcs record -A me --ignore-time -m p$i --all f+done++cd .. && mkdir temp2 && cd temp2 && darcs init++set -x++: Demonstrate problem with regrem+for i in $range+do+ darcs pull --patch p$i ../temp1 --all+ if darcs whatsnew ; then+ : Resolve conflict - rollback our patch+ darcs revert --all+ darcs rollback -a --patch-name x0+ darcs revert --all+ fi+ : Create local change and record it+ echo X$i > f && darcs record -A me --ignore-time -m x$i --all f+done++cd ..+rm -rf temp1 temp2
@@ -0,0 +1,30 @@+#!/usr/bin/env bash+set -ev++rm -rf temp1 temp2+mkdir temp1+cd temp1+darcs init+echo record author me > _darcs/prefs/defaults+echo ALL all >> _darcs/prefs/defaults+echo ALL verbose >> _darcs/prefs/defaults+echo ALL ignore-times >> _darcs/prefs/defaults+touch foo+darcs add foo+darcs whatsnew+darcs record -m add_foo+echo hello >> foo+darcs record -m mod_foo+cd ..+darcs get --repo-name temp2 temp1+cd temp2+cp ../temp1/_darcs/prefs/defaults _darcs/prefs+echo y/d/y | tr / \\012 | darcs unpull --interactive+test -f foo -a ! -s foo+echo hello >> foo+darcs record -m mod_foo_again+darcs pull ../temp1+test -s foo+cd ..+rm -rf temp1 temp2+
@@ -0,0 +1,30 @@+#!/usr/bin/env bash++set -ev++# Check that the right patches get sent using --dont-prompt-for-dependencies++rm -rf temp1+rm -rf temp2+mkdir temp2+mkdir temp1+cd temp2+darcs init+cd ..+cd temp1+darcs init+echo foo > f+darcs record -Ax -alm foo1+echo bar > b+darcs rec -Ax -alm bar1+echo foo2 > f+darcs record -Ax -alm foo2+echo bar2 > b+darcs record -Ax -alm bar2+echo y | darcs send ../temp2 --dont-prompt-for-dependencies -p foo2 --dry-run > toto+#on the previous line, we don't get asked about foo1.+grep foo toto | wc -l | grep 2+#but we send it anyway.+grep bar toto | wc -l | grep 0+cd ..+rm -rf temp1 temp2
@@ -0,0 +1,54 @@+#!/usr/bin/env bash+set -ev++DARCS_EDITOR=echo+export DARCS_EDITOR++rm -rf temp1 temp2+mkdir temp1 temp2++cd temp2+darcs init+cd ..++cd temp1+darcs init+date > foobar+darcs add foobar+darcs rec -a -m add-foobar++cat > saveit.sh <<EOF+#!/bin/sh+# send-mail1.sh: Test sendmail command for darcs send 1+# 2008-Oct-06 22.25 / TN+set -ev+echo all: \$0 "\$@" >>saved.out+echo \$6 contains: >>saved.out+ls -ltr >>saved.out+cat "\$6" >>saved.out+echo End of \$6 contents >>saved.out+grep add-foobar \$6+CNT=0+while [ "\$#" != "0" ]; do+ CNT=`expr \$CNT + 1`+ echo \$0: arg[\$CNT] = \"\$1\" >>saved.out+ shift+done+echo \$0: Total \$CNT arguments >>saved.out+echo \$0: Input: >>saved.out+cat >>saved.out+echo \$0: End of input: >>saved.out+EOF++chmod +x saveit.sh++# foobar+darcs send --author=me -a --to=random@random \+ --sendmail-command='bash ./saveit.sh %s %t %c %b %f %a %S %t %C %B %F %A something' ../temp2++cat saved.out+grep add-foobar saved.out+grep 'addfile ./foobar' saved.out++cd ..+rm -rf temp1 temp2
@@ -0,0 +1,52 @@+#!/usr/bin/env bash+set -ev++DARCS_EDITOR=echo+export DARCS_EDITOR++rm -rf temp1 temp2+mkdir temp1 temp2++cd temp2+darcs init++# setup test+cd ../temp1+darcs init+touch foo bar+darcs add foo bar+darcs record -a -m add_foo_bar -A x++# Test that a default preference value is not needed to send+darcs send --author=me -a --to=random@random --sendmail-command='grep "^To: random@random$" %<' ../temp2++# Test that a default preference will be used when no --to value is specified+echo "default@email" > ../temp2/_darcs/prefs/email+darcs send --author=me -a --sendmail-command='grep "^To: default@email$" %<' ../temp2++# Test that the --to parameter overrides the default value in the repository+darcs send --author=me -a --to=override@default --sendmail-command='grep "^To: override@default$" %<' ../temp2+darcs send --author=me -a --in-reply-to=some-thread-id --sendmail-command='grep "^In-Reply-To: some-thread-id$" %<' ../temp2+darcs send --author=me -a --in-reply-to=some-thread-id --sendmail-command='grep "^References: some-thread-id$" %<' ../temp2++# Test that the --subject parameter sets the subject++# Test that the --output-auto-name parameter outputs what we expect+darcs send --author=me -a --subject="it works" --output test1.dpatch ../temp2+darcs send --author=me -a --subject="it works" --output-auto-name ../temp2+cmp test1.dpatch add_foo_bar.dpatch++# test --output-auto-name works with optional argument.+mkdir patchdir+darcs send --author=me -a --subject="it works" --output-auto-name=patchdir ../temp2+cmp test1.dpatch patchdir/add_foo_bar.dpatch++# checking --output-auto-name=dir when run in different directory+cd patchdir+rm add_foo_bar.dpatch+darcs send --author=me -a --subject="it works" --output-auto-name=. ../../temp2+cmp ../test1.dpatch add_foo_bar.dpatch+cd ..++cd ..+rm -rf temp1 temp2
@@ -0,0 +1,38 @@+#!/usr/bin/env bash+set -ev++rm -rf temp1 temp2+mkdir temp1 temp2++cd temp2+darcs init++cd ../temp1+darcs init+touch foo bar+darcs add foo bar+darcs record -a -m add_foo_bar -A x+darcs mv foo zig+darcs mv bar foo+darcs mv zig bar+darcs record -a -m swap_foo_bar -A x+darcs send --author=me --output=funpatch --dont-sign -a ../temp2+cd ../temp2+darcs apply ../temp1/funpatch+cd ..+cmp temp1/bar temp2/bar+rm -rf temp2++mkdir temp2+cd temp2+darcs init+darcs apply ../temp1/funpatch+## Also test that "darcs apply" can accept a patch on stdin.+darcs obl -a+darcs apply < ../temp1/funpatch+cd ..+cmp temp1/bar temp2/bar+++rm -rf temp1 temp2+
@@ -0,0 +1,68 @@+#!/usr/bin/env bash++# Some tests for the --set-scripts-executable option.++. lib++abort_windows++rm -rf temp1 temp2++mkdir temp1+cd temp1+darcs init+cat > script.pl << FOO+#!/usr/bin/env perl+print "Hello\n";+FOO+chmod 0644 script.pl+date > nonscript+# pre-tests+test -r script.pl+test -r nonscript+test ! -x script.pl+test ! -x nonscript+darcs add script.pl nonscript+darcs record --patch-name 'uno' --all+cd ..++# sans --set-scripts-executable (should not be executable)+mkdir temp2+cd temp2+darcs init+darcs pull -a ../temp1+# sanity check+test -r script.pl+test -r nonscript+# nothing should be executable+test ! -x script.pl+test ! -x nonscript+cd ..+rm -rf temp2++# with --set-scripts-executable+mkdir temp2+cd temp2+darcs init+darcs pull -a ../temp1 --set-scripts-executable+# sanity check+test -r script.pl+test -r nonscript+# script should be executable+test -x script.pl+test ! -x nonscript+cd ..+rm -rf temp2++# now let's try the same thing with get+darcs get --set-scripts-executable temp1 temp2+cd temp2+# sanity check+test -r script.pl+test -r nonscript+# script should be executable+test -x script.pl+test ! -x nonscript+cd ..++rm -rf temp1 temp2
@@ -0,0 +1,24 @@+#!/usr/bin/env bash+++. lib++rm -rf temp1 temp2+mkdir temp1+cd temp1+darcs init+echo 'test file 1' > foo+darcs record --look-for-adds --all -m"Patch 1"+echo 'test boringfile' > bar+darcs setpref boringfile bar+darcs record --look-for-adds --all -m"Patch 2"+echo 'test file 3' > baz+# there should be no -R+darcs record --look-for-adds --all -m"Patch 3" > ../temp2+cat ../temp2+not grep R ../temp2++cd ..++rm -rf temp1+rm -f temp2
@@ -0,0 +1,25 @@+#!/usr/bin/env bash+set -ev++rm -rf temp1+mkdir temp1+cd temp1+darcs init+echo hello world > foo+darcs add foo+darcs record -a -m add -A x+echo goodbye world >> foo+darcs diff+darcs diff -u+darcs whatsnew+darcs whatsnew --summary+echo y | darcs revert -a+darcs show contents foo | cmp foo -+mkdir d+darcs add d+darcs record -a -m 'add dir' -A x+rmdir d+darcs revert -a d+cd ..+rm -rf temp1+
@@ -0,0 +1,30 @@+#!/usr/bin/env bash+set -ev++rm -rf test+mkdir test+cd test+darcs init+echo zig > foo+darcs add foo+darcs record -a -m add_foo -A x+echo zag >> foo+darcs record -a -m mod_foo -A y+echo bar > foo+darcs record -a -m mod2 -A y++darcs show authors > authors+grep x authors+grep y authors++head -1 authors > first-author+grep y first-author++darcs show authors --verbose+darcs show authors --verbose | grep y | wc -l > num-patches+cat num-patches+grep 2 num-patches++cd ..+rm -rf test+
@@ -0,0 +1,30 @@+#!/usr/bin/env bash++## All these commands SHOULD fail (hence leading NOTs).+. lib++rm -rf test+mkdir test+cd test++darcs show bug --debug 1> stdout 2> stderr || true++cat stdout+cat stderr++echo The following test will fail if this version of darcs is marked as+echo obsolete.+echo ==================================================================++not grep 'please do not' stderr++# The following test fails if HTTP isn't present, but would be a nice test+# to have in place.++#not grep unable stderr++grep 'fake bug' stderr++cd ..+rm -rf test+
@@ -0,0 +1,26 @@+#!/usr/bin/env bash+set -ev++rm -rf temp1+mkdir temp1+cd temp1+darcs init+touch foo+darcs add foo+echo first > foo+darcs record -a -m "first edit" -A author1+echo second > foo+darcs record -a -m "second edit" -A author2+darcs tag t1 -A moi+echo third > foo+darcs record -a -m "third edit" -A author3+echo fourth > foo+darcs record -a -m "fourth edit" -A author4+echo unrecorded > foo+darcs show contents foo | grep fourth+darcs show contents foo -p third | grep third+darcs show contents foo --match="author author1" first | grep first+darcs show contents foo --tag t1 | grep second+cd ..++rm -rf temp1
@@ -0,0 +1,35 @@+#!/usr/bin/env bash+set -ev++rm -rf temp1+mkdir temp1+cd temp1+darcs initialize+echo ALL ignore-times >> _darcs/prefs/defaults+echo A > foo+darcs add foo+darcs record -a -m AA -A x+echo B > foo+darcs record -a -m BB -A x+echo C > foo+darcs record -a -m CC -A x+darcs tag -m 1.0 -A x+echo D > foo+darcs record -a -m DD -A x+echo E > foo+darcs record -a -m EE -A x+echo F > foo+darcs record -a -m FF -A x+darcs tag -m 2.0 -A x++darcs show tags > my+cat my+cat > ref <<EOF+2.0+1.0+EOF++cmp my ref+cd ..++rm -rf temp1
@@ -0,0 +1,34 @@+#!/usr/bin/env bash+set -ev++DARCS_EDITOR=echo+export DARCS_EDITOR++rm -rf temp1+mkdir temp1++cd temp1+darcs init++echo hello world > foo+darcs add foo+darcs record -a --author=me -m "First patch"+i=1+while test $i -le 100; do+ echo foo $i > foo;+ darcs record --ignore-times -a --author=me -m "Patch $i" > bar+ i=`expr $i + 1`+done+darcs tag --author=me -m 'silly tag'+i=1000+while test $i -le 1096; do+ echo foo $i > foo;+ darcs record --ignore-times -a --author=me -m "Patch $i" > bar+ i=`expr $i + 1`+done++darcs --version+darcs changes | head++cd ..+# rm -rf temp1
@@ -0,0 +1,143 @@+#!/usr/bin/env bash+set -ev++if [ x${REMOTE_DIR} = x ]; then+ REMOTE_DIR=sshtest+fi++if [ x"${USE_PUTTY}" != x ]; then+ DARCS_SSH=plink+ export DARCS_SSH+ DARCS_SCP=pscp+ export DARCS_SCP+ DARCS_SFTP=psftp+ export DARCS_SFTP+fi++if [ x"${USE_CONTROL_MASTER}" != x ]; then+ DARCS_SSH_FLAGS="--ssh-cm"+ export DARCS_SSH_FLAGS+fi++if [ x"${DARCS_SSH}" = x ]; then+ SSH=ssh+else+ SSH=${DARCS_SSH}+fi++rm -rf tempssh+mkdir tempssh+cd tempssh++cleanup () {+ cd ..+ rm -rf tempssh+}++if [ x${REMOTE} = x ]; then+ echo+ echo "Note: to enable full SSH testing, set REMOTE to some SSH path first,"+ echo " e.g. REMOTE=you@server.org $0"+ cleanup+ exit 0 # exit normally in case we are being run from the shell harness+fi++# ================ Setting up remote repositories ===============+${SSH} ${REMOTE} "\+rm -rf ${REMOTE_DIR}; \+mkdir ${REMOTE_DIR}; \+cd ${REMOTE_DIR}; \+\+mkdir testrepo; cd testrepo; \+darcs init; \+echo moi > _darcs/prefs/author; \+touch a; darcs add a; darcs record a --ignore-times -am 'add file a'; \+echo 'first line' > a; darcs record a --ignore-times -am 'add first line to a'; \+cd ..; \+\+darcs get testrepo testrepo-pull; \+cd testrepo-pull; \+echo moi > _darcs/prefs/author; \+touch b; darcs add b; darcs record b --ignore-times -am 'add file b'; \+echo 'other line' > b; darcs record b --ignore-times -am 'add other line to b'; \+cd ..; \+\+darcs get testrepo testrepo-push; \+darcs get testrepo testrepo-send; \+"++# ================ Settings ===============+echo ${DARCS_SSH_FLAGS}+echo ${DARCS_SSH}+echo ${DARCS_SCP}+echo ${DARCS_SFTP}++# ================ Checking darcs get ==================+${DARCS} get ${DARCS_SSH_FLAGS} ${REMOTE}:${REMOTE_DIR}/testrepo ${DARCS_SSH_FLAGS}+# check that the test repo made it over+[ -d testrepo ]+[ -d testrepo/_darcs ]+[ -f testrepo/a ]++# if the above test is disabled we just init a blank repo+# so that the other tests can continue+if [ ! -d testrepo ]; then+ mkdir testrepo+ cd testrepo+ darcs init+ cd ..+fi++# ================ Checking darcs pull =================+${DARCS} get ${DARCS_SSH_FLAGS} testrepo testrepo-pull+cd testrepo-pull+echo yy | ${DARCS} pull ${DARCS_SSH_FLAGS} ${REMOTE}:${REMOTE_DIR}/testrepo-pull+# see if the changes got pulled over+grep "other line" b++cd ..++# ================ Checking darcs push and send ================="+${DARCS} get ${DARCS_SSH_FLAGS} testrepo testrepo-push+cd testrepo-push+echo moi > _darcs/prefs/author+echo "second line" >> a; ${DARCS} record a --ignore-times -am "add second line to a"+touch c; ${DARCS} add c+${DARCS} record --ignore-times -am "add file c" c+echo yy | ${DARCS} push ${DARCS_SSH_FLAGS} ${REMOTE}:${REMOTE_DIR}/testrepo-push+# check that the file c got pushed over+${SSH} ${REMOTE} "[ -f ${REMOTE_DIR}/testrepo-push/c ]"+echo yy | ${DARCS} send ${DARCS_SSH_FLAGS} ${REMOTE}:${REMOTE_DIR}/testrepo-send -o mybundle.dpatch+# check that the bundle was created+grep "add file c" mybundle.dpatch+cd ..++# ================ Checking darcs put =================="+cd testrepo+${DARCS} put ${DARCS_SSH_FLAGS} ${REMOTE}:${REMOTE_DIR}/testrepo-put+# check that the put was successful+${SSH} ${REMOTE} "[ -d ${REMOTE_DIR}/testrepo-put/_darcs ]"+${SSH} ${REMOTE} "[ -f ${REMOTE_DIR}/testrepo-put/a ]"+cd ..++# ======== Checking push over ssh with a conflict ========="+${SSH} ${REMOTE} "echo apply no-allow-conflicts >> ${REMOTE_DIR}/testrepo-put/_darcs/prefs/defaults"++cd testrepo +echo 'change for remote' > a+darcs record --ignore-times -am 'change for remote'+darcs push -a+darcs ob --last 1 -a+echo 'change for local' > a+darcs record --ignore-times -am 'change for local'++if darcs push -a 2>&1 | grep -q 'conflicts options to apply' ; then+ # do nothing. + echo "OK";+else+ exit 1;+fi++cd ..++cleanup
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff
file too large to diff