Repository: Gazler/githug Branch: master Commit: a613718f5625 Files: 413 Total size: 261.7 KB Directory structure: gitextract_yw6bj_tb/ ├── .gitignore ├── .rspec ├── .travis.yml ├── Dockerfile ├── Gemfile ├── LICENCE.txt ├── README.md ├── Rakefile ├── bin/ │ └── githug ├── githug.gemspec ├── levels/ │ ├── add.rb │ ├── bisect/ │ │ ├── .githug/ │ │ │ ├── COMMIT_EDITMSG │ │ │ ├── HEAD │ │ │ ├── ORIG_HEAD │ │ │ ├── config │ │ │ ├── description │ │ │ ├── gitk.cache │ │ │ ├── hooks/ │ │ │ │ ├── applypatch-msg.sample │ │ │ │ ├── commit-msg.sample │ │ │ │ ├── post-commit.sample │ │ │ │ ├── post-receive.sample │ │ │ │ ├── post-update.sample │ │ │ │ ├── pre-applypatch.sample │ │ │ │ ├── pre-commit.sample │ │ │ │ ├── pre-rebase.sample │ │ │ │ ├── prepare-commit-msg.sample │ │ │ │ └── update.sample │ │ │ ├── index │ │ │ ├── info/ │ │ │ │ ├── exclude │ │ │ │ └── refs │ │ │ ├── logs/ │ │ │ │ ├── HEAD │ │ │ │ └── refs/ │ │ │ │ └── heads/ │ │ │ │ └── master │ │ │ ├── objects/ │ │ │ │ ├── info/ │ │ │ │ │ └── packs │ │ │ │ └── pack/ │ │ │ │ ├── pack-59fab357f3158a9640633de6a3326ed79a2b4fe6.idx │ │ │ │ └── pack-59fab357f3158a9640633de6a3326ed79a2b4fe6.pack │ │ │ └── packed-refs │ │ ├── makefile │ │ ├── prog.rb │ │ └── test.rb │ ├── bisect.rb │ ├── blame/ │ │ ├── .githug/ │ │ │ ├── COMMIT_EDITMSG │ │ │ ├── HEAD │ │ │ ├── config │ │ │ ├── description │ │ │ ├── hooks/ │ │ │ │ ├── applypatch-msg.sample │ │ │ │ ├── commit-msg.sample │ │ │ │ ├── post-commit.sample │ │ │ │ ├── post-receive.sample │ │ │ │ ├── post-update.sample │ │ │ │ ├── pre-applypatch.sample │ │ │ │ ├── pre-commit.sample │ │ │ │ ├── pre-rebase.sample │ │ │ │ ├── prepare-commit-msg.sample │ │ │ │ └── update.sample │ │ │ ├── index │ │ │ ├── info/ │ │ │ │ └── exclude │ │ │ ├── logs/ │ │ │ │ ├── HEAD │ │ │ │ └── refs/ │ │ │ │ └── heads/ │ │ │ │ └── master │ │ │ ├── objects/ │ │ │ │ ├── 00/ │ │ │ │ │ └── d6bf5341b263ccaf32e7973be55126eb30a343 │ │ │ │ ├── 05/ │ │ │ │ │ └── 07c26fed4d111a8344763be9af68af90f0ecf2 │ │ │ │ ├── 09/ │ │ │ │ │ └── 4094808dc6dc336c93c8602190a9e5f7bd6a11 │ │ │ │ ├── 21/ │ │ │ │ │ └── 15d78864000292628872941b14521f90187eed │ │ │ │ ├── 31/ │ │ │ │ │ └── 11dda1f5b08d50ac44b99acabfa54f1e6e72b0 │ │ │ │ ├── 50/ │ │ │ │ │ └── 8db115ba34a0e4e8667653aebe0265bb4f7e80 │ │ │ │ ├── 5e/ │ │ │ │ │ └── 8863df752e3b7f2150df7c78f12bef6f1ff00e │ │ │ │ ├── 67/ │ │ │ │ │ └── 788a4b90180c7588d7bd0ad8032957b0f429ba │ │ │ │ ├── 70/ │ │ │ │ │ └── d00535a3a25b0ac1736dd3d306d6271e5427ed │ │ │ │ ├── 97/ │ │ │ │ │ └── bdd0cccf9f4b8730f78cb53a81a74f205dbcc2 │ │ │ │ ├── ab/ │ │ │ │ │ └── 08819ba3ffaeba17d4f870dc3a458a904519f4 │ │ │ │ ├── be/ │ │ │ │ │ └── 96fe46de646f6a5c728f90cc884aef96fa1d6f │ │ │ │ ├── cd/ │ │ │ │ │ └── 9c6b9ab1a6f56cccc69b6aa661f1d67ba5fb46 │ │ │ │ ├── dd/ │ │ │ │ │ └── df1d8ebd60eec169c15a5b23cb49a58d2ed5a0 │ │ │ │ └── ff/ │ │ │ │ └── d39c2dbfd94bdbca06d48686e0cbda642f3de7 │ │ │ └── refs/ │ │ │ └── heads/ │ │ │ └── master │ │ └── config.rb │ ├── blame.rb │ ├── branch.rb │ ├── branch_at.rb │ ├── checkout.rb │ ├── checkout_file.rb │ ├── checkout_tag.rb │ ├── checkout_tag_over_branch.rb │ ├── cherry-pick/ │ │ ├── .githug/ │ │ │ ├── COMMIT_EDITMSG │ │ │ ├── HEAD │ │ │ ├── config │ │ │ ├── description │ │ │ ├── hooks/ │ │ │ │ ├── applypatch-msg.sample │ │ │ │ ├── commit-msg.sample │ │ │ │ ├── post-update.sample │ │ │ │ ├── pre-applypatch.sample │ │ │ │ ├── pre-commit.sample │ │ │ │ ├── pre-rebase.sample │ │ │ │ ├── prepare-commit-msg.sample │ │ │ │ └── update.sample │ │ │ ├── index │ │ │ ├── info/ │ │ │ │ └── exclude │ │ │ ├── logs/ │ │ │ │ ├── HEAD │ │ │ │ └── refs/ │ │ │ │ └── heads/ │ │ │ │ ├── master │ │ │ │ └── new-feature │ │ │ ├── objects/ │ │ │ │ ├── 05/ │ │ │ │ │ └── aa97588aff673dcf00e0e1b835d6ec6207a7d9 │ │ │ │ ├── 14/ │ │ │ │ │ └── ad8f1413c40e0e5be6f6cbdec392a783c1d754 │ │ │ │ ├── 1b/ │ │ │ │ │ └── 912962174dd58fbbf1627ec816fa6672c70854 │ │ │ │ ├── 22/ │ │ │ │ │ └── f99f3abfd02190a760388c8845c190247859fe │ │ │ │ ├── 23/ │ │ │ │ │ └── 2d266a78d5ef7196f1ede14972ccf7ee19e587 │ │ │ │ ├── 31/ │ │ │ │ │ └── 68bdc3ae7e0ab987dda238beab9c0582c7fc07 │ │ │ │ ├── 49/ │ │ │ │ │ └── 4fa3fc1c15986f9a950aba8607da591223614c │ │ │ │ ├── 4a/ │ │ │ │ │ └── 1961bce62840eaef9c4392fe5cc799e38c9b7b │ │ │ │ ├── 54/ │ │ │ │ │ ├── 22c2405527c05c23b243b2535a97b6cefa1427 │ │ │ │ │ └── f3308533fd4400c94c4cd5827cd38036a67a76 │ │ │ │ ├── 58/ │ │ │ │ │ └── a8c8edcfdd00c6d8cce9aada8f987a1677571f │ │ │ │ ├── 6e/ │ │ │ │ │ └── dea632d9540e060bca97dda0897df2b7da0ec0 │ │ │ │ ├── 85/ │ │ │ │ │ └── 4e7ac38b3e8df02ea1480ec45aa9e7865f03d7 │ │ │ │ ├── 93/ │ │ │ │ │ └── 3a97260a11a5ee49bb8a4b7b097cb2fb38f85c │ │ │ │ ├── 9a/ │ │ │ │ │ └── e59e88e6af362c069847e621b971bafff158ac │ │ │ │ ├── a2/ │ │ │ │ │ └── 14badc644facc4722083aedf91d9339ffdffbf │ │ │ │ ├── b3/ │ │ │ │ │ └── 0c6a965415df6aef5f2903f9892fa5481152fc │ │ │ │ ├── c6/ │ │ │ │ │ └── 0c612734f25b9c989d954621491e9ca3c9e061 │ │ │ │ ├── ca/ │ │ │ │ │ └── 32a6dac7b6f97975edbe19a4296c2ee7682f68 │ │ │ │ ├── cf/ │ │ │ │ │ └── d8ce38c22c5fe83cc04e23f94646464f20d990 │ │ │ │ ├── d5/ │ │ │ │ │ ├── 02e68bfe397140e4ac2a819b138545abca604e │ │ │ │ │ └── 123e58b37fd886dff2b96bfe5ec08a498192aa │ │ │ │ └── ea/ │ │ │ │ ├── 2a47c19b85fc321e2737ddc49db3deeba3a1b5 │ │ │ │ └── 3dbcc5e2d2359698c3606b0ec44af9f76def54 │ │ │ └── refs/ │ │ │ └── heads/ │ │ │ ├── master │ │ │ └── new-feature │ │ ├── README.md │ │ ├── hardcore-math.js │ │ └── nokia.js │ ├── cherry-pick.rb │ ├── clone.rb │ ├── clone_to_folder.rb │ ├── commit.rb │ ├── commit_amend.rb │ ├── commit_in_future.rb │ ├── config.rb │ ├── conflict/ │ │ ├── .githug/ │ │ │ ├── COMMIT_EDITMSG │ │ │ ├── HEAD │ │ │ ├── config │ │ │ ├── description │ │ │ ├── hooks/ │ │ │ │ ├── applypatch-msg.sample │ │ │ │ ├── commit-msg.sample │ │ │ │ ├── post-update.sample │ │ │ │ ├── pre-applypatch.sample │ │ │ │ ├── pre-commit.sample │ │ │ │ ├── pre-rebase.sample │ │ │ │ ├── prepare-commit-msg.sample │ │ │ │ └── update.sample │ │ │ ├── index │ │ │ ├── info/ │ │ │ │ └── exclude │ │ │ ├── logs/ │ │ │ │ ├── HEAD │ │ │ │ └── refs/ │ │ │ │ └── heads/ │ │ │ │ ├── master │ │ │ │ └── mybranch │ │ │ ├── objects/ │ │ │ │ ├── 1d/ │ │ │ │ │ └── b9aa5013e47f1482ec90323d926171a99c28af │ │ │ │ ├── 25/ │ │ │ │ │ └── b3f9c339430b35ae5822b2ef90e763284dc007 │ │ │ │ ├── 2d/ │ │ │ │ │ └── 0d90051e320215f54f357e746c9838490557e7 │ │ │ │ ├── 30/ │ │ │ │ │ ├── 6868e3258be1f35ae43db71e3a6d7edf42ffe7 │ │ │ │ │ └── cc28e66966109bb5bfbe96d6c817c367d2050a │ │ │ │ ├── 38/ │ │ │ │ │ └── 21e4362c5a76db1112b46b9210670cd5bab482 │ │ │ │ ├── 3c/ │ │ │ │ │ └── b65bef44ae724ddf9e89640e7e2754dea1a47f │ │ │ │ ├── 3d/ │ │ │ │ │ └── 7aec017559be2b61cab850dafdcb2b6212f1c3 │ │ │ │ ├── 40/ │ │ │ │ │ └── e20a455ac2731ad25c297b03aa543d7eedf6ab │ │ │ │ ├── 44/ │ │ │ │ │ └── 30fd3f45c832e685350417600a9862c94d20f1 │ │ │ │ ├── 46/ │ │ │ │ │ └── 677964e679f3e727356d0860c643a12a87902b │ │ │ │ ├── 50/ │ │ │ │ │ └── a127cb066eb903a6fa59d71802c10cb442fb3b │ │ │ │ ├── 55/ │ │ │ │ │ └── ad5af2a93afa778971e3d04faf20f6c368b6ba │ │ │ │ ├── 6b/ │ │ │ │ │ └── 0c0b32bdca3af9beb831744cb755d6fdc7c7c0 │ │ │ │ ├── 75/ │ │ │ │ │ └── 179304f4fab00613f08a9412b6cb0965bfa564 │ │ │ │ ├── 7c/ │ │ │ │ │ └── 36daf29660ae4a2f09345427ef76f1d38f902f │ │ │ │ ├── 88/ │ │ │ │ │ └── e0473c9da347c6311f5f8eca8d256bf25402b6 │ │ │ │ ├── bd/ │ │ │ │ │ └── c7bec8acae9b3eabf0a15b223a48211b7a89a1 │ │ │ │ ├── c7/ │ │ │ │ │ └── 97f979cf24ba148bf10d5e26f5d7402dd5f2e1 │ │ │ │ └── da/ │ │ │ │ └── ae380200ed6eeaafd926177018e8ff3009b988 │ │ │ └── refs/ │ │ │ └── heads/ │ │ │ ├── master │ │ │ └── mybranch │ │ └── poem.txt │ ├── conflict.rb │ ├── contribute.rb │ ├── delete_branch/ │ │ ├── .githug/ │ │ │ ├── COMMIT_EDITMSG │ │ │ ├── HEAD │ │ │ ├── config │ │ │ ├── description │ │ │ ├── hooks/ │ │ │ │ ├── applypatch-msg.sample │ │ │ │ ├── commit-msg.sample │ │ │ │ ├── post-update.sample │ │ │ │ ├── pre-applypatch.sample │ │ │ │ ├── pre-commit.sample │ │ │ │ ├── pre-push.sample │ │ │ │ ├── pre-rebase.sample │ │ │ │ ├── prepare-commit-msg.sample │ │ │ │ └── update.sample │ │ │ ├── index │ │ │ ├── info/ │ │ │ │ └── exclude │ │ │ ├── logs/ │ │ │ │ ├── HEAD │ │ │ │ └── refs/ │ │ │ │ └── heads/ │ │ │ │ ├── delete_me │ │ │ │ └── master │ │ │ ├── objects/ │ │ │ │ ├── b6/ │ │ │ │ │ └── 0afe294eb3c200d646995c9e0234470157c1b0 │ │ │ │ ├── e6/ │ │ │ │ │ └── 9de29bb2d1d6434b8b29ae775ad8c2e48c5391 │ │ │ │ └── e8/ │ │ │ │ └── 0ad49ace82167de62e498622d70377d913c79e │ │ │ └── refs/ │ │ │ └── heads/ │ │ │ ├── delete_me │ │ │ └── master │ │ └── readme │ ├── delete_branch.rb │ ├── diff/ │ │ ├── .githug/ │ │ │ ├── COMMIT_EDITMSG │ │ │ ├── HEAD │ │ │ ├── config │ │ │ ├── description │ │ │ ├── hooks/ │ │ │ │ ├── applypatch-msg.sample │ │ │ │ ├── commit-msg.sample │ │ │ │ ├── post-commit.sample │ │ │ │ ├── post-receive.sample │ │ │ │ ├── post-update.sample │ │ │ │ ├── pre-applypatch.sample │ │ │ │ ├── pre-commit.sample │ │ │ │ ├── pre-rebase.sample │ │ │ │ ├── prepare-commit-msg.sample │ │ │ │ └── update.sample │ │ │ ├── index │ │ │ ├── info/ │ │ │ │ └── exclude │ │ │ ├── logs/ │ │ │ │ ├── HEAD │ │ │ │ └── refs/ │ │ │ │ └── heads/ │ │ │ │ └── master │ │ │ ├── objects/ │ │ │ │ ├── 1b/ │ │ │ │ │ └── 6582364621c92707b587409cedbc4f77fc0cee │ │ │ │ ├── 4f/ │ │ │ │ │ └── 703ca9bd25781b6758eeb3c42ed5348610ba6d │ │ │ │ └── dc/ │ │ │ │ └── aa55e97af34402e84d5336da37abcccc23cba6 │ │ │ └── refs/ │ │ │ └── heads/ │ │ │ └── master │ │ └── app.rb │ ├── diff.rb │ ├── fetch.rb │ ├── find_old_branch/ │ │ ├── .githug/ │ │ │ ├── COMMIT_EDITMSG │ │ │ ├── HEAD │ │ │ ├── ORIG_HEAD │ │ │ ├── config │ │ │ ├── description │ │ │ ├── hooks/ │ │ │ │ ├── applypatch-msg.sample │ │ │ │ ├── commit-msg.sample │ │ │ │ ├── post-commit.sample │ │ │ │ ├── post-receive.sample │ │ │ │ ├── post-update.sample │ │ │ │ ├── pre-applypatch.sample │ │ │ │ ├── pre-commit.sample │ │ │ │ ├── pre-rebase.sample │ │ │ │ ├── prepare-commit-msg.sample │ │ │ │ └── update.sample │ │ │ ├── index │ │ │ ├── info/ │ │ │ │ └── exclude │ │ │ ├── logs/ │ │ │ │ ├── HEAD │ │ │ │ └── refs/ │ │ │ │ └── heads/ │ │ │ │ ├── blowup_sun_for_ransom │ │ │ │ ├── cure_common_cold │ │ │ │ ├── kill_the_batman │ │ │ │ └── solve_world_hunger │ │ │ ├── objects/ │ │ │ │ ├── 05/ │ │ │ │ │ └── e9c01bd3c9264761dd0cde477400a2c3104642 │ │ │ │ ├── 1f/ │ │ │ │ │ └── 0a136ddd98f61934d15eb00444df32d8e8254b │ │ │ │ ├── 32/ │ │ │ │ │ └── 4336a8401afc8ca384eaafe6615c84d552dd2c │ │ │ │ ├── 50/ │ │ │ │ │ └── 72aab6bd73accec89ad3aa077f43aab8a5e507 │ │ │ │ ├── 5d/ │ │ │ │ │ └── 130caf89f1a4bba6a1fffe72c484f3ab659e08 │ │ │ │ ├── 68/ │ │ │ │ │ └── 76e5b41fb693190df76b1baef6ef98623b4f1a │ │ │ │ ├── 6a/ │ │ │ │ │ └── 7702145d1eb91c9f79583eabb984027b12e60c │ │ │ │ ├── 89/ │ │ │ │ │ └── 4a16d6f1a48224e9006b4a6f0fe3846da19bec │ │ │ │ ├── b8/ │ │ │ │ │ └── c67b45e5fe9e4b39ac7ade725673f7c90bdfc3 │ │ │ │ └── bf/ │ │ │ │ └── 76434bc7e7346c6fef5a98aee0f7cfc0788f34 │ │ │ └── refs/ │ │ │ └── heads/ │ │ │ ├── blowup_sun_for_ransom │ │ │ ├── cure_common_cold │ │ │ ├── kill_the_batman │ │ │ └── solve_world_hunger │ │ ├── TODO │ │ └── myfile.txt │ ├── find_old_branch.rb │ ├── grep/ │ │ ├── .githug/ │ │ │ ├── COMMIT_EDITMSG │ │ │ ├── HEAD │ │ │ ├── config │ │ │ ├── description │ │ │ ├── hooks/ │ │ │ │ ├── applypatch-msg.sample │ │ │ │ ├── commit-msg.sample │ │ │ │ ├── post-commit.sample │ │ │ │ ├── post-receive.sample │ │ │ │ ├── post-update.sample │ │ │ │ ├── pre-applypatch.sample │ │ │ │ ├── pre-commit.sample │ │ │ │ ├── pre-rebase.sample │ │ │ │ ├── prepare-commit-msg.sample │ │ │ │ └── update.sample │ │ │ ├── index │ │ │ ├── info/ │ │ │ │ └── exclude │ │ │ ├── logs/ │ │ │ │ ├── HEAD │ │ │ │ └── refs/ │ │ │ │ └── heads/ │ │ │ │ └── master │ │ │ ├── objects/ │ │ │ │ ├── 12/ │ │ │ │ │ └── c702f8b25b6b528cf904670b854dba3eba0f45 │ │ │ │ ├── 6f/ │ │ │ │ │ └── 45753f4a16b69f5b2215f2dbe8245f073353cc │ │ │ │ ├── a4/ │ │ │ │ │ └── 1fe0c342be5c9930328cbb4315acebbd9c94b2 │ │ │ │ └── d3/ │ │ │ │ └── f53e82aa015d1eea3b06c3b62dfbacee83bbe1 │ │ │ └── refs/ │ │ │ └── heads/ │ │ │ └── master │ │ ├── app.rb │ │ └── config.rb │ ├── grep.rb │ ├── ignore.rb │ ├── include.rb │ ├── init.rb │ ├── log.rb │ ├── merge/ │ │ ├── .githug/ │ │ │ ├── COMMIT_EDITMSG │ │ │ ├── HEAD │ │ │ ├── config │ │ │ ├── description │ │ │ ├── hooks/ │ │ │ │ ├── applypatch-msg.sample │ │ │ │ ├── commit-msg.sample │ │ │ │ ├── post-commit.sample │ │ │ │ ├── post-receive.sample │ │ │ │ ├── post-update.sample │ │ │ │ ├── pre-applypatch.sample │ │ │ │ ├── pre-commit.sample │ │ │ │ ├── pre-rebase.sample │ │ │ │ ├── prepare-commit-msg.sample │ │ │ │ └── update.sample │ │ │ ├── index │ │ │ ├── info/ │ │ │ │ └── exclude │ │ │ ├── logs/ │ │ │ │ ├── HEAD │ │ │ │ └── refs/ │ │ │ │ └── heads/ │ │ │ │ ├── feature │ │ │ │ └── master │ │ │ ├── objects/ │ │ │ │ ├── ad/ │ │ │ │ │ └── 24149d789e59d4b5f9ce41cda90110ca0f98b7 │ │ │ │ ├── ae/ │ │ │ │ │ └── fde3a01f6e10d72fd4899ce14c8b2654d3eb45 │ │ │ │ ├── cc/ │ │ │ │ │ └── 8ea5a233df119d025eb240b9470e1ca76a151c │ │ │ │ ├── e1/ │ │ │ │ │ └── 2277fe88657a072f1c4eb7d9320e4e6a74ba95 │ │ │ │ └── e6/ │ │ │ │ └── 9de29bb2d1d6434b8b29ae775ad8c2e48c5391 │ │ │ └── refs/ │ │ │ └── heads/ │ │ │ ├── feature │ │ │ └── master │ │ └── file1 │ ├── merge.rb │ ├── merge_squash.rb │ ├── number_of_files_committed.rb │ ├── pull.rb │ ├── push.rb │ ├── push_branch.rb │ ├── push_tags.rb │ ├── rebase/ │ │ ├── .githug/ │ │ │ ├── COMMIT_EDITMSG │ │ │ ├── HEAD │ │ │ ├── ORIG_HEAD │ │ │ ├── config │ │ │ ├── index │ │ │ ├── logs/ │ │ │ │ ├── HEAD │ │ │ │ └── refs/ │ │ │ │ └── heads/ │ │ │ │ ├── feature │ │ │ │ └── master │ │ │ ├── objects/ │ │ │ │ ├── 0c/ │ │ │ │ │ └── d212c5b28da2e65ed4900712dd36c8adce48ad │ │ │ │ ├── 44/ │ │ │ │ │ └── 19b972c0cd1b346ac90332aa7c5cc949589f78 │ │ │ │ ├── 54/ │ │ │ │ │ └── 3b9bebdc6bd5c4b22136034a95dd097a57d3dd │ │ │ │ ├── 81/ │ │ │ │ │ └── 78c76d627cade75005b40711b92f4177bc6cfc │ │ │ │ ├── 98/ │ │ │ │ │ └── 205e9faf10cf33d2ef7c0f66e402540c62613a │ │ │ │ ├── a7/ │ │ │ │ │ └── 8bcab6232e9382a86436cdfcb2ed0391b1f0ac │ │ │ │ ├── b7/ │ │ │ │ │ └── 7313d7be366609dd2e77aa96d7fd73f4e27853 │ │ │ │ ├── b9/ │ │ │ │ │ └── 2d5d55d379cfb90b750e6472fc983f32ad9a71 │ │ │ │ ├── e6/ │ │ │ │ │ └── 9de29bb2d1d6434b8b29ae775ad8c2e48c5391 │ │ │ │ └── ed/ │ │ │ │ └── 0fdcf366b21b8984fb37ea34106978a2e5c5ba │ │ │ └── refs/ │ │ │ └── heads/ │ │ │ ├── feature │ │ │ └── master │ │ └── README │ ├── rebase.rb │ ├── rebase_onto.rb │ ├── remote.rb │ ├── remote_add.rb │ ├── remote_url.rb │ ├── rename.rb │ ├── rename_commit.rb │ ├── reorder.rb │ ├── repack.rb │ ├── reset.rb │ ├── reset_soft.rb │ ├── restore.rb │ ├── restructure.rb │ ├── revert.rb │ ├── rm.rb │ ├── rm_cached.rb │ ├── squash.rb │ ├── stage_lines.rb │ ├── stash/ │ │ ├── .githug/ │ │ │ ├── COMMIT_EDITMSG │ │ │ ├── HEAD │ │ │ ├── config │ │ │ ├── description │ │ │ ├── hooks/ │ │ │ │ ├── applypatch-msg.sample │ │ │ │ ├── commit-msg.sample │ │ │ │ ├── post-update.sample │ │ │ │ ├── pre-applypatch.sample │ │ │ │ ├── pre-commit.sample │ │ │ │ ├── pre-rebase.sample │ │ │ │ ├── prepare-commit-msg.sample │ │ │ │ └── update.sample │ │ │ ├── index │ │ │ ├── info/ │ │ │ │ └── exclude │ │ │ ├── logs/ │ │ │ │ ├── HEAD │ │ │ │ └── refs/ │ │ │ │ └── heads/ │ │ │ │ └── master │ │ │ ├── objects/ │ │ │ │ ├── 02/ │ │ │ │ │ └── 060592b31c9e12ffe1b282addf9537c5ef8e1f │ │ │ │ ├── 4b/ │ │ │ │ │ └── 825dc642cb6eb9a060e54bf8d69288fbee4904 │ │ │ │ ├── 7f/ │ │ │ │ │ └── 82d7e4fba66980af16da540e18d8955518cdc2 │ │ │ │ └── 85/ │ │ │ │ └── e560abcd7e3255dcd91982476e432f4d3d1b51 │ │ │ └── refs/ │ │ │ └── heads/ │ │ │ └── master │ │ └── lyrics.txt │ ├── stash.rb │ ├── status.rb │ ├── submodule.rb │ └── tag.rb ├── lib/ │ ├── githug/ │ │ ├── cli.rb │ │ ├── extensions/ │ │ │ └── grit/ │ │ │ └── ruby1.9.rb │ │ ├── game.rb │ │ ├── level.rb │ │ ├── profile.rb │ │ ├── repository.rb │ │ ├── ui.rb │ │ └── version.rb │ └── githug.rb └── spec/ ├── githug/ │ ├── cli_spec.rb │ ├── game_spec.rb │ ├── level_spec.rb │ ├── profile_spec.rb │ ├── repository_spec.rb │ └── ui_spec.rb ├── githug_spec.rb ├── spec_helper.rb └── support/ └── files/ └── test_level.rb ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ *.gem .bundle Gemfile.lock pkg/* *.swp .rvmrc git_hug/* .profile.yml ================================================ FILE: .rspec ================================================ --color --order default ================================================ FILE: .travis.yml ================================================ language: ruby dist: focal rvm: - 2.4.10 - 2.5.9 - 2.6.8 - 2.7.4 - 3.0.1 - ruby-head matrix: allow_failures: - rvm: ruby-head before_install: - gem update --system - git config --global user.email 'user@example.com' - git config --global user.name 'Test User' ================================================ FILE: Dockerfile ================================================ FROM ruby:slim LABEL org.opencontainers.image.authors="diraneyya@ip.rwth-aachen.de" # This is in order to have the man pages during exercises RUN sed -i '/path-exclude \/usr\/share\/man/d' /etc/dpkg/dpkg.cfg.d/docker RUN sed -i '/path-exclude \/usr\/share\/groff/d' /etc/dpkg/dpkg.cfg.d/docker RUN apt update && apt install -y man git && apt install --reinstall coreutils # The DATA_PATH is used for the deliverables, or the submissible # content for the classroom activity. ENV DATA_PATH="/data" # The REPO_PATH is where the original or the teacher's forked repo # resides inside the container. ENV REPO_PATH="/root/githug" # The LEVEL_PATH is where the current challenge resides and where # students should navigate prior to attemping to use the `githug` # commands. ENV LEVEL_PATH="/git_hug" ENV GITHUG_GITCONF="/root/.gitconfig" ENV GITHUG_PROFILE="$LEVEL_PATH/.profile.yml" ENV GITHUG_HISTORY_OUTPUT="$DATA_PATH/history.txt" ENV GITHUG_PROFILE_OUTPUT="$DATA_PATH/profile.yml" ENV GITHUG_GITCONF_OUTPUT="$DATA_PATH/gitconfig" RUN mkdir -p $DATA_PATH ADD . $REPO_PATH WORKDIR $REPO_PATH RUN gem build RUN gem install *.gem WORKDIR / RUN echo "y" | $GEM_HOME/bin/githug RUN cp $GITHUG_PROFILE $GITHUG_PROFILE_OUTPUT WORKDIR $LEVEL_PATH # The bash login script in below clears the history and restores # progress using the contents of the $GITHUG_PROFILE_OUTPUT file. RUN printf "history -c\nHISTSIZE= \nHISTFILESIZE= \n\ echo '--- NEW SESSION ---' >> $GITHUG_HISTORY_OUTPUT \n\ if ! [ -e $GITHUG_PROFILE_OUTPUT ]; then \n\ echo 'ERROR: Corrupt level progress data. Exiting.' \n\ echo '>>> CORRUPT DATA <<<' >> $GITHUG_HISTORY_OUTPUT \n\ exit 1; fi \n\ cp $GITHUG_GITCONF_OUTPUT $GITHUG_GITCONF \n\ mkdir -p $LEVEL_PATH && cp $GITHUG_PROFILE_OUTPUT $GITHUG_PROFILE \n\ export PATH=\"\$GEM_HOME/bin:\$PATH\" \n\ cd $LEVEL_PATH && githug reset \n\ echo -e '\nIMPORTANT: everything you type in this container is \ recorded to assist in the grading process.' \n" >> ~/.bash_profile RUN printf "history -a\ncat \$HISTFILE >> $GITHUG_HISTORY_OUTPUT \n\ cp $GITHUG_PROFILE $GITHUG_PROFILE_OUTPUT \n\ cp $GITHUG_GITCONF $GITHUG_GITCONF_OUTPUT 2>/dev/null \n\ cp $GITHUG_PROFILE $GITHUG_PROFILE_OUTPUT \n" >> ~/.bash_logout ENTRYPOINT ["/bin/bash", "--login"] VOLUME $DATA_PATH ================================================ FILE: Gemfile ================================================ source "http://rubygems.org" # Specify your gem's dependencies in githug.gemspec gemspec ================================================ FILE: LICENCE.txt ================================================ Copyright (c) 2013 Gary 'Gazler' Rennie MIT License 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. ================================================ FILE: README.md ================================================ # Githug Git Your Game On [![Build Status](https://travis-ci.org/Gazler/githug.svg?branch=master)](https://travis-ci.org/Gazler/githug) [![Code Climate](https://codeclimate.com/github/Gazler/githug.svg)](https://codeclimate.com/github/Gazler/githug) ## About Githug is designed to give you a practical way of learning git. It has a series of levels, each requiring you to use git commands to arrive at a correct answer. ## Playing Githug Githug should work on Linux, OS X and Windows. ### Prerequisites Githug requires Ruby 1.8.7 or higher. You can check which version of Ruby is installed with the following command: ``` ruby --version ``` If ruby is not installed, follow the installation instructions on [ruby-lang.org](https://www.ruby-lang.org/en/documentation/installation/). ### Installation To install Githug, run gem install githug If you get a complaint about permissions, you can rerun the command with `sudo`: sudo gem install githug #### Usage with Docker An unofficial _Docker_ image for this project by [@odiraneyya](https://github.com/odiraneyya) is available on ([Docker Hub](https://hub.docker.com/r/orwa84/githug)). ### Starting the Game After the gem is installed change directory to the location where you want the game-related assets to be stored. Then run `githug`: githug You will be prompted to create a directory. No githug directory found, do you wish to create one? [yn] Type `y` (yes) to continue, `n` (no) to cancel and quit Githug. ### Commands Githug has 4 game commands: * play - The default command, checks your solution for the current level * hint - Gives you a hint (if available) for the current level * reset - Reset the current level or reset the level to a given name or path * levels - List all the levels ## Change Log The change log is available on the wiki. [Change log](https://github.com/Gazler/githug/wiki/Change-Log) ## Contributing To suggest a level or create a level that has been suggested, check out [the wiki](https://github.com/Gazler/githug/wiki). Get yourself on the [contributors list](https://github.com/Gazler/githug/contributors) by doing the following: * Fork the repository * Make a level in the levels directory (covered below) * Add your level to the LEVELS array inside `lib/githug/level.rb` in a position that makes sense (the "commit" level after the "add" and "init" levels for example) * Make sure your level works (covered below) * Submit a pull request ## Todo List * A follow-up to the level, more information on a specific command, etc. * More levels! ## Writing Levels Githug has a DSL for writing levels. Here is an example: ```ruby difficulty 1 description "There is a file in your folder called README, you should add it to your staging area" setup do repo.init FileUtils.touch("README") end solution do return false unless repo.status.files.keys.include?("README") return false if repo.status.files["README"].untracked true end hint do puts "You can type `git` in your shell to get a list of available git commands" end ``` `difficulty`, `description` and `solution` are required. You can include multiple hints like this: ```ruby hints [ "You can type `git` in your shell to get a list of available git commands", "Check the man for `git add`"] ``` By default, `setup` will remove all files from the game folder. You do not need to include a setup method if you don't want an initial git repository (if you are testing `git init` or only checking an answer.) You can call `repo.init` to initialize an empty repository. All methods called on `repo` are sent to the [grit gem](https://github.com/mojombo/grit) if the method does not exist, and you can use that for most git related commands (`repo.add`, `repo.commit`, etc.). Another method exists called `init_from_level` and it is used like so: ```ruby setup do init_from_level end ``` This will copy the contents of a repository specified in the levels folder for your level. For example, if your level is called "merge" then it will copy the contents of the "merge" folder. It is recommended that you perform the following steps: * mkdir "yourlevel" * cd "yourlevel" * git init * some git stuff * **important** rename ".git" to ".githug" so that it isn't treated as a submodule * cd "../" * git add "yourlevel" After doing this, your level should be able to copy the contents from that git repository and use those for your level. See the "blame" level for an example of this. ## Testing Levels The easiest way to test a level is: * Change into your git_hug repository * Run `githug reset PATH_TO_YOUR_LEVEL` * Solve the level * Run `githug test PATH_TO_YOUR_LEVEL` Please note that the `githug test` command can be run as `githug test --errors` to get an error stack trace from your solve method. It would be ideal if you add an integration test for your level. These tests live in `spec/githug_spec` and **must** be run in order. If you add a level but do not add a test, please add a simple `skip_level` test case similar to the `contribute` level. ## FAQs 1. Answers are not being checked properly *This is a common issue we are facing and we are actively working to fix it.* For now, run the following commands to change the default branch name to master. This should fix most of the issues you may face. ``` $ git config --global init.defaultBranch master $ githug reset ``` From the current level forward, the default branch will be `master`. 2. `githug` command doesn't work Githug currently isn't supported on ruby versions ^3.0.0. Use any ruby version below 3.0.0 (preferrably 2.7.1). If you use rvm, execute the below commands ``` $ rvm install 2.7.1 $ rvm use 2.7.1 ``` If you use rbenv, execute the below commands ``` $ rbenv install 2.7.1 $ rbenv global 2.7.1 ``` ================================================ FILE: Rakefile ================================================ require "bundler/gem_tasks" require 'rspec/core/rake_task' RSpec::Core::RakeTask.new(:spec) task :default => [:spec] ================================================ FILE: bin/githug ================================================ #!/usr/bin/env ruby require 'githug/cli' Githug::CLI.start ================================================ FILE: githug.gemspec ================================================ # -*- encoding: utf-8 -*- $:.push File.expand_path("../lib", __FILE__) require "githug/version" Gem::Specification.new do |s| s.name = "githug" s.version = Githug::VERSION s.authors = ["Gary Rennie"] s.email = ["gazler@gmail.com"] s.homepage = "https://github.com/Gazler/githug" s.summary = %q{An interactive way to learn git.} s.description = %q{An interactive way to learn git.} s.rubyforge_project = "githug" s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } s.require_paths = ["lib"] # specify any dependencies here; for example: s.add_development_dependency "rspec", "~>2.8.0" s.add_dependency "grit", "~>2.3.0" s.add_dependency "thor", "~>0.14.6" s.add_dependency "rake", "<11" end ================================================ FILE: levels/add.rb ================================================ difficulty 1 description "There is a file in your folder called `README`; add it to your staging area. Note: Each level starts with a new repo. Don't look for files of the previous one." setup do repo.init FileUtils.touch("README") system "git branch -m master" end solution do return false unless repo.status.files.keys.include?("README") return false if repo.status.files["README"].untracked true end hint do puts "You can type `git` in your shell to get a list of available git commands." end ================================================ FILE: levels/bisect/.githug/COMMIT_EDITMSG ================================================ Another Commit ================================================ FILE: levels/bisect/.githug/HEAD ================================================ ref: refs/heads/master ================================================ FILE: levels/bisect/.githug/ORIG_HEAD ================================================ f351ca63a759f56bb26924fd566294eb23455c71 ================================================ FILE: levels/bisect/.githug/config ================================================ [core] repositoryformatversion = 0 filemode = true bare = false logallrefupdates = true [pack] deltaCacheSize = 1 ================================================ FILE: levels/bisect/.githug/description ================================================ Unnamed repository; edit this file 'description' to name the repository. ================================================ FILE: levels/bisect/.githug/gitk.cache ================================================ 1 1 f351ca63a759f56bb26924fd566294eb23455c71 f608824888b83bbedc1f658be7496ffea467a8fb {7f8406e742c5281ec2c9bc3c6cc69dc6ba5311fd 36da01fb0571360b0e1170f0cb46e74f72927cda c1b80f5ed4995cda8e19027f0776351af6b76703 4145057e11ab90109c95882b1f40d560da408bfa 94e162b505bc2290fb67764357a625192f8a4e8a bfb16eec1081387b586dc8009ef422cfff60b622 c0a1cdff8dd63948a5fc41f5871681c5b133c053 e8dd08d5aabb62b56c80320672cd8d42e2b8954d 89d27c5f4680cd21028947be75c33a68511decb9 98d617d3bdf0b6c59d6177233acb4b9fd54b7aac 0b194f30d6867522d1666590087e6c0b5e20dc93 d406b1b8bc269c9aef9127299e83947220e7a9b0 f608824888b83bbedc1f658be7496ffea467a8fb} 1 ================================================ FILE: levels/bisect/.githug/hooks/applypatch-msg.sample ================================================ #!/bin/sh # # An example hook script to check the commit log message taken by # applypatch from an e-mail message. # # The hook should exit with non-zero status after issuing an # appropriate message if it wants to stop the commit. The hook is # allowed to edit the commit message file. # # To enable this hook, rename this file to "applypatch-msg". . git-sh-setup test -x "$GIT_DIR/hooks/commit-msg" && exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"} : ================================================ FILE: levels/bisect/.githug/hooks/commit-msg.sample ================================================ #!/bin/sh # # An example hook script to check the commit log message. # Called by "git commit" with one argument, the name of the file # that has the commit message. The hook should exit with non-zero # status after issuing an appropriate message if it wants to stop the # commit. The hook is allowed to edit the commit message file. # # To enable this hook, rename this file to "commit-msg". # Uncomment the below to add a Signed-off-by line to the message. # Doing this in a hook is a bad idea in general, but the prepare-commit-msg # hook is more suited to it. # # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" # This example catches duplicate Signed-off-by lines. test "" = "$(grep '^Signed-off-by: ' "$1" | sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { echo >&2 Duplicate Signed-off-by lines. exit 1 } ================================================ FILE: levels/bisect/.githug/hooks/post-commit.sample ================================================ #!/bin/sh # # An example hook script that is called after a successful # commit is made. # # To enable this hook, rename this file to "post-commit". : Nothing ================================================ FILE: levels/bisect/.githug/hooks/post-receive.sample ================================================ #!/bin/sh # # An example hook script for the "post-receive" event. # # The "post-receive" script is run after receive-pack has accepted a pack # and the repository has been updated. It is passed arguments in through # stdin in the form # # For example: # aa453216d1b3e49e7f6f98441fa56946ddcd6a20 68f7abf4e6f922807889f52bc043ecd31b79f814 refs/heads/master # # see contrib/hooks/ for a sample, or uncomment the next line and # rename the file to "post-receive". #. /usr/share/doc/git-core/contrib/hooks/post-receive-email ================================================ FILE: levels/bisect/.githug/hooks/post-update.sample ================================================ #!/bin/sh # # An example hook script to prepare a packed repository for use over # dumb transports. # # To enable this hook, rename this file to "post-update". exec git update-server-info ================================================ FILE: levels/bisect/.githug/hooks/pre-applypatch.sample ================================================ #!/bin/sh # # An example hook script to verify what is about to be committed # by applypatch from an e-mail message. # # The hook should exit with non-zero status after issuing an # appropriate message if it wants to stop the commit. # # To enable this hook, rename this file to "pre-applypatch". . git-sh-setup test -x "$GIT_DIR/hooks/pre-commit" && exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"} : ================================================ FILE: levels/bisect/.githug/hooks/pre-commit.sample ================================================ #!/bin/sh # # An example hook script to verify what is about to be committed. # Called by "git commit" with no arguments. The hook should # exit with non-zero status after issuing an appropriate message if # it wants to stop the commit. # # To enable this hook, rename this file to "pre-commit". if git rev-parse --verify HEAD >/dev/null 2>&1 then against=HEAD else # Initial commit: diff against an empty tree object against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 fi # If you want to allow non-ascii filenames set this variable to true. allownonascii=$(git config hooks.allownonascii) # Cross platform projects tend to avoid non-ascii filenames; prevent # them from being added to the repository. We exploit the fact that the # printable range starts at the space character and ends with tilde. if [ "$allownonascii" != "true" ] && # Note that the use of brackets around a tr range is ok here, (it's # even required, for portability to Solaris 10's /usr/bin/tr), since # the square bracket bytes happen to fall in the designated range. test "$(git diff --cached --name-only --diff-filter=A -z $against | LC_ALL=C tr -d '[ -~]\0')" then echo "Error: Attempt to add a non-ascii file name." echo echo "This can cause problems if you want to work" echo "with people on other platforms." echo echo "To be portable it is advisable to rename the file ..." echo echo "If you know what you are doing you can disable this" echo "check using:" echo echo " git config hooks.allownonascii true" echo exit 1 fi exec git diff-index --check --cached $against -- ================================================ FILE: levels/bisect/.githug/hooks/pre-rebase.sample ================================================ #!/bin/sh # # Copyright (c) 2006, 2008 Junio C Hamano # # The "pre-rebase" hook is run just before "git rebase" starts doing # its job, and can prevent the command from running by exiting with # non-zero status. # # The hook is called with the following parameters: # # $1 -- the upstream the series was forked from. # $2 -- the branch being rebased (or empty when rebasing the current branch). # # This sample shows how to prevent topic branches that are already # merged to 'next' branch from getting rebased, because allowing it # would result in rebasing already published history. publish=next basebranch="$1" if test "$#" = 2 then topic="refs/heads/$2" else topic=`git symbolic-ref HEAD` || exit 0 ;# we do not interrupt rebasing detached HEAD fi case "$topic" in refs/heads/??/*) ;; *) exit 0 ;# we do not interrupt others. ;; esac # Now we are dealing with a topic branch being rebased # on top of master. Is it OK to rebase it? # Does the topic really exist? git show-ref -q "$topic" || { echo >&2 "No such branch $topic" exit 1 } # Is topic fully merged to master? not_in_master=`git rev-list --pretty=oneline ^master "$topic"` if test -z "$not_in_master" then echo >&2 "$topic is fully merged to master; better remove it." exit 1 ;# we could allow it, but there is no point. fi # Is topic ever merged to next? If so you should not be rebasing it. only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` only_next_2=`git rev-list ^master ${publish} | sort` if test "$only_next_1" = "$only_next_2" then not_in_topic=`git rev-list "^$topic" master` if test -z "$not_in_topic" then echo >&2 "$topic is already up-to-date with master" exit 1 ;# we could allow it, but there is no point. else exit 0 fi else not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` /usr/bin/perl -e ' my $topic = $ARGV[0]; my $msg = "* $topic has commits already merged to public branch:\n"; my (%not_in_next) = map { /^([0-9a-f]+) /; ($1 => 1); } split(/\n/, $ARGV[1]); for my $elem (map { /^([0-9a-f]+) (.*)$/; [$1 => $2]; } split(/\n/, $ARGV[2])) { if (!exists $not_in_next{$elem->[0]}) { if ($msg) { print STDERR $msg; undef $msg; } print STDERR " $elem->[1]\n"; } } ' "$topic" "$not_in_next" "$not_in_master" exit 1 fi exit 0 <<\DOC_END ################################################################ This sample hook safeguards topic branches that have been published from being rewound. The workflow assumed here is: * Once a topic branch forks from "master", "master" is never merged into it again (either directly or indirectly). * Once a topic branch is fully cooked and merged into "master", it is deleted. If you need to build on top of it to correct earlier mistakes, a new topic branch is created by forking at the tip of the "master". This is not strictly necessary, but it makes it easier to keep your history simple. * Whenever you need to test or publish your changes to topic branches, merge them into "next" branch. The script, being an example, hardcodes the publish branch name to be "next", but it is trivial to make it configurable via $GIT_DIR/config mechanism. With this workflow, you would want to know: (1) ... if a topic branch has ever been merged to "next". Young topic branches can have stupid mistakes you would rather clean up before publishing, and things that have not been merged into other branches can be easily rebased without affecting other people. But once it is published, you would not want to rewind it. (2) ... if a topic branch has been fully merged to "master". Then you can delete it. More importantly, you should not build on top of it -- other people may already want to change things related to the topic as patches against your "master", so if you need further changes, it is better to fork the topic (perhaps with the same name) afresh from the tip of "master". Let's look at this example: o---o---o---o---o---o---o---o---o---o "next" / / / / / a---a---b A / / / / / / / / c---c---c---c B / / / / \ / / / / b---b C \ / / / / / \ / ---o---o---o---o---o---o---o---o---o---o---o "master" A, B and C are topic branches. * A has one fix since it was merged up to "next". * B has finished. It has been fully merged up to "master" and "next", and is ready to be deleted. * C has not merged to "next" at all. We would want to allow C to be rebased, refuse A, and encourage B to be deleted. To compute (1): git rev-list ^master ^topic next git rev-list ^master next if these match, topic has not merged in next at all. To compute (2): git rev-list master..topic if this is empty, it is fully merged to "master". DOC_END ================================================ FILE: levels/bisect/.githug/hooks/prepare-commit-msg.sample ================================================ #!/bin/sh # # An example hook script to prepare the commit log message. # Called by "git commit" with the name of the file that has the # commit message, followed by the description of the commit # message's source. The hook's purpose is to edit the commit # message file. If the hook fails with a non-zero status, # the commit is aborted. # # To enable this hook, rename this file to "prepare-commit-msg". # This hook includes three examples. The first comments out the # "Conflicts:" part of a merge commit. # # The second includes the output of "git diff --name-status -r" # into the message, just before the "git status" output. It is # commented because it doesn't cope with --amend or with squashed # commits. # # The third example adds a Signed-off-by line to the message, that can # still be edited. This is rarely a good idea. case "$2,$3" in merge,) /usr/bin/perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;; # ,|template,) # /usr/bin/perl -i.bak -pe ' # print "\n" . `git diff --cached --name-status -r` # if /^#/ && $first++ == 0' "$1" ;; *) ;; esac # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" ================================================ FILE: levels/bisect/.githug/hooks/update.sample ================================================ #!/bin/sh # # An example hook script to blocks unannotated tags from entering. # Called by "git receive-pack" with arguments: refname sha1-old sha1-new # # To enable this hook, rename this file to "update". # # Config # ------ # hooks.allowunannotated # This boolean sets whether unannotated tags will be allowed into the # repository. By default they won't be. # hooks.allowdeletetag # This boolean sets whether deleting tags will be allowed in the # repository. By default they won't be. # hooks.allowmodifytag # This boolean sets whether a tag may be modified after creation. By default # it won't be. # hooks.allowdeletebranch # This boolean sets whether deleting branches will be allowed in the # repository. By default they won't be. # hooks.denycreatebranch # This boolean sets whether remotely creating branches will be denied # in the repository. By default this is allowed. # # --- Command line refname="$1" oldrev="$2" newrev="$3" # --- Safety check if [ -z "$GIT_DIR" ]; then echo "Don't run this script from the command line." >&2 echo " (if you want, you could supply GIT_DIR then run" >&2 echo " $0 )" >&2 exit 1 fi if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then echo "Usage: $0 " >&2 exit 1 fi # --- Config allowunannotated=$(git config --bool hooks.allowunannotated) allowdeletebranch=$(git config --bool hooks.allowdeletebranch) denycreatebranch=$(git config --bool hooks.denycreatebranch) allowdeletetag=$(git config --bool hooks.allowdeletetag) allowmodifytag=$(git config --bool hooks.allowmodifytag) # check for no description projectdesc=$(sed -e '1q' "$GIT_DIR/description") case "$projectdesc" in "Unnamed repository"* | "") echo "*** Project description file hasn't been set" >&2 exit 1 ;; esac # --- Check types # if $newrev is 0000...0000, it's a commit to delete a ref. zero="0000000000000000000000000000000000000000" if [ "$newrev" = "$zero" ]; then newrev_type=delete else newrev_type=$(git cat-file -t $newrev) fi case "$refname","$newrev_type" in refs/tags/*,commit) # un-annotated tag short_refname=${refname##refs/tags/} if [ "$allowunannotated" != "true" ]; then echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2 echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 exit 1 fi ;; refs/tags/*,delete) # delete tag if [ "$allowdeletetag" != "true" ]; then echo "*** Deleting a tag is not allowed in this repository" >&2 exit 1 fi ;; refs/tags/*,tag) # annotated tag if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 then echo "*** Tag '$refname' already exists." >&2 echo "*** Modifying a tag is not allowed in this repository." >&2 exit 1 fi ;; refs/heads/*,commit) # branch if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then echo "*** Creating a branch is not allowed in this repository" >&2 exit 1 fi ;; refs/heads/*,delete) # delete branch if [ "$allowdeletebranch" != "true" ]; then echo "*** Deleting a branch is not allowed in this repository" >&2 exit 1 fi ;; refs/remotes/*,commit) # tracking branch ;; refs/remotes/*,delete) # delete tracking branch if [ "$allowdeletebranch" != "true" ]; then echo "*** Deleting a tracking branch is not allowed in this repository" >&2 exit 1 fi ;; *) # Anything else (is there anything else?) echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 exit 1 ;; esac # --- Finished exit 0 ================================================ FILE: levels/bisect/.githug/info/exclude ================================================ # git ls-files --others --exclude-from=.git/info/exclude # Lines that start with '#' are comments. # For a project mostly in C, the following would be a good set of # exclude patterns (uncomment them if you want to use them): # *.[oa] # *~ ================================================ FILE: levels/bisect/.githug/info/refs ================================================ 94e162b505bc2290fb67764357a625192f8a4e8a refs/bisect/bad bfb16eec1081387b586dc8009ef422cfff60b622 refs/bisect/good-bfb16eec1081387b586dc8009ef422cfff60b622 c0a1cdff8dd63948a5fc41f5871681c5b133c053 refs/bisect/good-c0a1cdff8dd63948a5fc41f5871681c5b133c053 f608824888b83bbedc1f658be7496ffea467a8fb refs/bisect/good-f608824888b83bbedc1f658be7496ffea467a8fb 12628f463f4c722695bf0e9d603c9411287885db refs/heads/master 32ae79196bd49a3416264970b0cd873e26bd1824 refs/tags/graetsch 89d27c5f4680cd21028947be75c33a68511decb9 refs/tags/graetsch^{} 2a5197909a0a1575145e0a40bb13dd54f5d6a9f7 refs/tags/orig_head f351ca63a759f56bb26924fd566294eb23455c71 refs/tags/orig_head^{} ================================================ FILE: levels/bisect/.githug/logs/HEAD ================================================ f351ca63a759f56bb26924fd566294eb23455c71 f608824888b83bbedc1f658be7496ffea467a8fb Florian Sesser 1348697815 +0200 reset: moving to f608824888b83bbedc1f658be7496ffea467a8fb f608824888b83bbedc1f658be7496ffea467a8fb f608824888b83bbedc1f658be7496ffea467a8fb Florian Sesser 1348697842 +0200 checkout: moving from master to hacklschorsch f608824888b83bbedc1f658be7496ffea467a8fb 80a9b3d94237f982b6c9052e6d56b930f18a4ef5 Florian Sesser 1348697952 +0200 cherry-pick: Another Commit 80a9b3d94237f982b6c9052e6d56b930f18a4ef5 8c992afff5e16c97f4ef82d58671a3403d734086 Florian Sesser 1348697967 +0200 cherry-pick: Another Commit 8c992afff5e16c97f4ef82d58671a3403d734086 49774ea84ae3723cc4fac75521435cc04d56b657 Florian Sesser 1348698291 +0200 cherry-pick: Another Commit 49774ea84ae3723cc4fac75521435cc04d56b657 e060c0d789288fda946f91254672295230b2de9d Florian Sesser 1348698316 +0200 cherry-pick: Another Commit e060c0d789288fda946f91254672295230b2de9d ffb097e3edfa828afa565eeceee6b506b3f2a131 Florian Sesser 1348698720 +0200 commit: Another Commit ffb097e3edfa828afa565eeceee6b506b3f2a131 2e1735d5bef6db0f3e325051a179af280f05573a Florian Sesser 1348698824 +0200 commit: Another Commit 2e1735d5bef6db0f3e325051a179af280f05573a ccddb96f824a0e929f5fecf55c0f4479552246f3 Florian Sesser 1348698884 +0200 commit: Another Commit ccddb96f824a0e929f5fecf55c0f4479552246f3 a530e7ed25173d0800cfe33cc8915e5929209b8e Florian Sesser 1348698911 +0200 commit: Another Commit a530e7ed25173d0800cfe33cc8915e5929209b8e fdbfc0d403e5ac0b2659cbfa2cbb061fcca0dc2a Florian Sesser 1348699468 +0200 commit: Another Commit fdbfc0d403e5ac0b2659cbfa2cbb061fcca0dc2a 5d1eb75377072c5c6e5a1b0ac4159181ecc4edff Florian Sesser 1348699592 +0200 commit: Another Commit 5d1eb75377072c5c6e5a1b0ac4159181ecc4edff 9f54462abbb991b167532929b34118113aa6c52e Florian Sesser 1348699604 +0200 cherry-pick: Another Commit 9f54462abbb991b167532929b34118113aa6c52e 7c03a99ba384572c216769f0273b5baf3ba83694 Florian Sesser 1348699614 +0200 cherry-pick: Another Commit 7c03a99ba384572c216769f0273b5baf3ba83694 5db7a7cb90e745e2c9dbdd84810ccc7d91d92e72 Florian Sesser 1348699625 +0200 cherry-pick: Another Commit 5db7a7cb90e745e2c9dbdd84810ccc7d91d92e72 18ed2ac1522a014412d4303ce7c8db39becab076 Florian Sesser 1348699635 +0200 cherry-pick: Another Commit 18ed2ac1522a014412d4303ce7c8db39becab076 bb736ddd9b83d6296d23444a2ab3b0d2fa6dfb81 Florian Sesser 1348699648 +0200 cherry-pick: Another Commit bb736ddd9b83d6296d23444a2ab3b0d2fa6dfb81 888386c77c957dc52f3113f2483663e3132559d4 Florian Sesser 1348699657 +0200 cherry-pick: Another Commit 888386c77c957dc52f3113f2483663e3132559d4 028763b396121e035f672ef5af75d2dcb1cc8146 Florian Sesser 1348699668 +0200 cherry-pick: Another Commit 028763b396121e035f672ef5af75d2dcb1cc8146 979576184c5ec9667cf7593cf550c420378e960f Florian Sesser 1348699679 +0200 cherry-pick: Another Commit 979576184c5ec9667cf7593cf550c420378e960f 12628f463f4c722695bf0e9d603c9411287885db Florian Sesser 1348699693 +0200 cherry-pick: Another Commit ================================================ FILE: levels/bisect/.githug/logs/refs/heads/master ================================================ 0000000000000000000000000000000000000000 f608824888b83bbedc1f658be7496ffea467a8fb Florian Sesser 1348697842 +0200 branch: Created from HEAD f608824888b83bbedc1f658be7496ffea467a8fb 80a9b3d94237f982b6c9052e6d56b930f18a4ef5 Florian Sesser 1348697952 +0200 cherry-pick: Another Commit 80a9b3d94237f982b6c9052e6d56b930f18a4ef5 8c992afff5e16c97f4ef82d58671a3403d734086 Florian Sesser 1348697967 +0200 cherry-pick: Another Commit 8c992afff5e16c97f4ef82d58671a3403d734086 49774ea84ae3723cc4fac75521435cc04d56b657 Florian Sesser 1348698291 +0200 cherry-pick: Another Commit 49774ea84ae3723cc4fac75521435cc04d56b657 e060c0d789288fda946f91254672295230b2de9d Florian Sesser 1348698316 +0200 cherry-pick: Another Commit e060c0d789288fda946f91254672295230b2de9d ffb097e3edfa828afa565eeceee6b506b3f2a131 Florian Sesser 1348698720 +0200 commit: Another Commit ffb097e3edfa828afa565eeceee6b506b3f2a131 2e1735d5bef6db0f3e325051a179af280f05573a Florian Sesser 1348698824 +0200 commit: Another Commit 2e1735d5bef6db0f3e325051a179af280f05573a ccddb96f824a0e929f5fecf55c0f4479552246f3 Florian Sesser 1348698884 +0200 commit: Another Commit ccddb96f824a0e929f5fecf55c0f4479552246f3 a530e7ed25173d0800cfe33cc8915e5929209b8e Florian Sesser 1348698911 +0200 commit: Another Commit a530e7ed25173d0800cfe33cc8915e5929209b8e fdbfc0d403e5ac0b2659cbfa2cbb061fcca0dc2a Florian Sesser 1348699468 +0200 commit: Another Commit fdbfc0d403e5ac0b2659cbfa2cbb061fcca0dc2a 5d1eb75377072c5c6e5a1b0ac4159181ecc4edff Florian Sesser 1348699592 +0200 commit: Another Commit 5d1eb75377072c5c6e5a1b0ac4159181ecc4edff 9f54462abbb991b167532929b34118113aa6c52e Florian Sesser 1348699604 +0200 cherry-pick: Another Commit 9f54462abbb991b167532929b34118113aa6c52e 7c03a99ba384572c216769f0273b5baf3ba83694 Florian Sesser 1348699614 +0200 cherry-pick: Another Commit 7c03a99ba384572c216769f0273b5baf3ba83694 5db7a7cb90e745e2c9dbdd84810ccc7d91d92e72 Florian Sesser 1348699625 +0200 cherry-pick: Another Commit 5db7a7cb90e745e2c9dbdd84810ccc7d91d92e72 18ed2ac1522a014412d4303ce7c8db39becab076 Florian Sesser 1348699635 +0200 cherry-pick: Another Commit 18ed2ac1522a014412d4303ce7c8db39becab076 bb736ddd9b83d6296d23444a2ab3b0d2fa6dfb81 Florian Sesser 1348699648 +0200 cherry-pick: Another Commit bb736ddd9b83d6296d23444a2ab3b0d2fa6dfb81 888386c77c957dc52f3113f2483663e3132559d4 Florian Sesser 1348699657 +0200 cherry-pick: Another Commit 888386c77c957dc52f3113f2483663e3132559d4 028763b396121e035f672ef5af75d2dcb1cc8146 Florian Sesser 1348699668 +0200 cherry-pick: Another Commit 028763b396121e035f672ef5af75d2dcb1cc8146 979576184c5ec9667cf7593cf550c420378e960f Florian Sesser 1348699679 +0200 cherry-pick: Another Commit 979576184c5ec9667cf7593cf550c420378e960f 12628f463f4c722695bf0e9d603c9411287885db Florian Sesser 1348699693 +0200 cherry-pick: Another Commit 12628f463f4c722695bf0e9d603c9411287885db 12628f463f4c722695bf0e9d603c9411287885db Florian Sesser 1348701064 +0200 Branch: renamed refs/heads/hacklschorsch to refs/heads/master ================================================ FILE: levels/bisect/.githug/objects/info/packs ================================================ P pack-59fab357f3158a9640633de6a3326ed79a2b4fe6.pack ================================================ FILE: levels/bisect/.githug/packed-refs ================================================ # pack-refs with: peeled 94e162b505bc2290fb67764357a625192f8a4e8a refs/bisect/bad bfb16eec1081387b586dc8009ef422cfff60b622 refs/bisect/good-bfb16eec1081387b586dc8009ef422cfff60b622 c0a1cdff8dd63948a5fc41f5871681c5b133c053 refs/bisect/good-c0a1cdff8dd63948a5fc41f5871681c5b133c053 f608824888b83bbedc1f658be7496ffea467a8fb refs/bisect/good-f608824888b83bbedc1f658be7496ffea467a8fb 12628f463f4c722695bf0e9d603c9411287885db refs/heads/master 32ae79196bd49a3416264970b0cd873e26bd1824 refs/tags/graetsch ^89d27c5f4680cd21028947be75c33a68511decb9 2a5197909a0a1575145e0a40bb13dd54f5d6a9f7 refs/tags/orig_head ^f351ca63a759f56bb26924fd566294eb23455c71 ================================================ FILE: levels/bisect/makefile ================================================ test: ruby prog.rb 5 | ruby test.rb ================================================ FILE: levels/bisect/prog.rb ================================================ #!/usr/bin/env ruby puts Integer((Integer(ARGV[0])-3)*9)-1 ================================================ FILE: levels/bisect/test.rb ================================================ #!/usr/bin/env ruby line = gets exit 1 if line != "15\n" ================================================ FILE: levels/bisect.rb ================================================ difficulty 3 description "A bug was introduced somewhere along the way. You know that running `ruby prog.rb 5` should output 15. You can also run `make test`. What are the first 7 chars of the hash of the commit (the abbreviated hash) that introduced the bug?" setup do init_from_level repo.init system "git branch -m master" end solution do "18ed2ac" == request("What are the first 7 characters of the hash of the commit that introduced the bug?")[0..6] end hint do puts ["The fastest way to find the bug is with bisect.", "Don't forget to start bisect first, identify a good or bad commit, then run `git bisect run make test`."] end ================================================ FILE: levels/blame/.githug/COMMIT_EDITMSG ================================================ added more options (no really) ================================================ FILE: levels/blame/.githug/HEAD ================================================ ref: refs/heads/master ================================================ FILE: levels/blame/.githug/config ================================================ [core] repositoryformatversion = 0 filemode = true bare = false logallrefupdates = true ================================================ FILE: levels/blame/.githug/description ================================================ Unnamed repository; edit this file 'description' to name the repository. ================================================ FILE: levels/blame/.githug/hooks/applypatch-msg.sample ================================================ #!/bin/sh # # An example hook script to check the commit log message taken by # applypatch from an e-mail message. # # The hook should exit with non-zero status after issuing an # appropriate message if it wants to stop the commit. The hook is # allowed to edit the commit message file. # # To enable this hook, rename this file to "applypatch-msg". . git-sh-setup test -x "$GIT_DIR/hooks/commit-msg" && exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"} : ================================================ FILE: levels/blame/.githug/hooks/commit-msg.sample ================================================ #!/bin/sh # # An example hook script to check the commit log message. # Called by "git commit" with one argument, the name of the file # that has the commit message. The hook should exit with non-zero # status after issuing an appropriate message if it wants to stop the # commit. The hook is allowed to edit the commit message file. # # To enable this hook, rename this file to "commit-msg". # Uncomment the below to add a Signed-off-by line to the message. # Doing this in a hook is a bad idea in general, but the prepare-commit-msg # hook is more suited to it. # # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" # This example catches duplicate Signed-off-by lines. test "" = "$(grep '^Signed-off-by: ' "$1" | sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { echo >&2 Duplicate Signed-off-by lines. exit 1 } ================================================ FILE: levels/blame/.githug/hooks/post-commit.sample ================================================ #!/bin/sh # # An example hook script that is called after a successful # commit is made. # # To enable this hook, rename this file to "post-commit". : Nothing ================================================ FILE: levels/blame/.githug/hooks/post-receive.sample ================================================ #!/bin/sh # # An example hook script for the "post-receive" event. # # The "post-receive" script is run after receive-pack has accepted a pack # and the repository has been updated. It is passed arguments in through # stdin in the form # # For example: # aa453216d1b3e49e7f6f98441fa56946ddcd6a20 68f7abf4e6f922807889f52bc043ecd31b79f814 refs/heads/master # # see contrib/hooks/ for a sample, or uncomment the next line and # rename the file to "post-receive". #. /usr/share/doc/git-core/contrib/hooks/post-receive-email ================================================ FILE: levels/blame/.githug/hooks/post-update.sample ================================================ #!/bin/sh # # An example hook script to prepare a packed repository for use over # dumb transports. # # To enable this hook, rename this file to "post-update". exec git update-server-info ================================================ FILE: levels/blame/.githug/hooks/pre-applypatch.sample ================================================ #!/bin/sh # # An example hook script to verify what is about to be committed # by applypatch from an e-mail message. # # The hook should exit with non-zero status after issuing an # appropriate message if it wants to stop the commit. # # To enable this hook, rename this file to "pre-applypatch". . git-sh-setup test -x "$GIT_DIR/hooks/pre-commit" && exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"} : ================================================ FILE: levels/blame/.githug/hooks/pre-commit.sample ================================================ #!/bin/sh # # An example hook script to verify what is about to be committed. # Called by "git commit" with no arguments. The hook should # exit with non-zero status after issuing an appropriate message if # it wants to stop the commit. # # To enable this hook, rename this file to "pre-commit". if git rev-parse --verify HEAD >/dev/null 2>&1 then against=HEAD else # Initial commit: diff against an empty tree object against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 fi # If you want to allow non-ascii filenames set this variable to true. allownonascii=$(git config hooks.allownonascii) # Cross platform projects tend to avoid non-ascii filenames; prevent # them from being added to the repository. We exploit the fact that the # printable range starts at the space character and ends with tilde. if [ "$allownonascii" != "true" ] && # Note that the use of brackets around a tr range is ok here, (it's # even required, for portability to Solaris 10's /usr/bin/tr), since # the square bracket bytes happen to fall in the designated range. test "$(git diff --cached --name-only --diff-filter=A -z $against | LC_ALL=C tr -d '[ -~]\0')" then echo "Error: Attempt to add a non-ascii file name." echo echo "This can cause problems if you want to work" echo "with people on other platforms." echo echo "To be portable it is advisable to rename the file ..." echo echo "If you know what you are doing you can disable this" echo "check using:" echo echo " git config hooks.allownonascii true" echo exit 1 fi exec git diff-index --check --cached $against -- ================================================ FILE: levels/blame/.githug/hooks/pre-rebase.sample ================================================ #!/bin/sh # # Copyright (c) 2006, 2008 Junio C Hamano # # The "pre-rebase" hook is run just before "git rebase" starts doing # its job, and can prevent the command from running by exiting with # non-zero status. # # The hook is called with the following parameters: # # $1 -- the upstream the series was forked from. # $2 -- the branch being rebased (or empty when rebasing the current branch). # # This sample shows how to prevent topic branches that are already # merged to 'next' branch from getting rebased, because allowing it # would result in rebasing already published history. publish=next basebranch="$1" if test "$#" = 2 then topic="refs/heads/$2" else topic=`git symbolic-ref HEAD` || exit 0 ;# we do not interrupt rebasing detached HEAD fi case "$topic" in refs/heads/??/*) ;; *) exit 0 ;# we do not interrupt others. ;; esac # Now we are dealing with a topic branch being rebased # on top of master. Is it OK to rebase it? # Does the topic really exist? git show-ref -q "$topic" || { echo >&2 "No such branch $topic" exit 1 } # Is topic fully merged to master? not_in_master=`git rev-list --pretty=oneline ^master "$topic"` if test -z "$not_in_master" then echo >&2 "$topic is fully merged to master; better remove it." exit 1 ;# we could allow it, but there is no point. fi # Is topic ever merged to next? If so you should not be rebasing it. only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` only_next_2=`git rev-list ^master ${publish} | sort` if test "$only_next_1" = "$only_next_2" then not_in_topic=`git rev-list "^$topic" master` if test -z "$not_in_topic" then echo >&2 "$topic is already up-to-date with master" exit 1 ;# we could allow it, but there is no point. else exit 0 fi else not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` /usr/bin/perl -e ' my $topic = $ARGV[0]; my $msg = "* $topic has commits already merged to public branch:\n"; my (%not_in_next) = map { /^([0-9a-f]+) /; ($1 => 1); } split(/\n/, $ARGV[1]); for my $elem (map { /^([0-9a-f]+) (.*)$/; [$1 => $2]; } split(/\n/, $ARGV[2])) { if (!exists $not_in_next{$elem->[0]}) { if ($msg) { print STDERR $msg; undef $msg; } print STDERR " $elem->[1]\n"; } } ' "$topic" "$not_in_next" "$not_in_master" exit 1 fi exit 0 ################################################################ This sample hook safeguards topic branches that have been published from being rewound. The workflow assumed here is: * Once a topic branch forks from "master", "master" is never merged into it again (either directly or indirectly). * Once a topic branch is fully cooked and merged into "master", it is deleted. If you need to build on top of it to correct earlier mistakes, a new topic branch is created by forking at the tip of the "master". This is not strictly necessary, but it makes it easier to keep your history simple. * Whenever you need to test or publish your changes to topic branches, merge them into "next" branch. The script, being an example, hardcodes the publish branch name to be "next", but it is trivial to make it configurable via $GIT_DIR/config mechanism. With this workflow, you would want to know: (1) ... if a topic branch has ever been merged to "next". Young topic branches can have stupid mistakes you would rather clean up before publishing, and things that have not been merged into other branches can be easily rebased without affecting other people. But once it is published, you would not want to rewind it. (2) ... if a topic branch has been fully merged to "master". Then you can delete it. More importantly, you should not build on top of it -- other people may already want to change things related to the topic as patches against your "master", so if you need further changes, it is better to fork the topic (perhaps with the same name) afresh from the tip of "master". Let's look at this example: o---o---o---o---o---o---o---o---o---o "next" / / / / / a---a---b A / / / / / / / / c---c---c---c B / / / / \ / / / / b---b C \ / / / / / \ / ---o---o---o---o---o---o---o---o---o---o---o "master" A, B and C are topic branches. * A has one fix since it was merged up to "next". * B has finished. It has been fully merged up to "master" and "next", and is ready to be deleted. * C has not merged to "next" at all. We would want to allow C to be rebased, refuse A, and encourage B to be deleted. To compute (1): git rev-list ^master ^topic next git rev-list ^master next if these match, topic has not merged in next at all. To compute (2): git rev-list master..topic if this is empty, it is fully merged to "master". ================================================ FILE: levels/blame/.githug/hooks/prepare-commit-msg.sample ================================================ #!/bin/sh # # An example hook script to prepare the commit log message. # Called by "git commit" with the name of the file that has the # commit message, followed by the description of the commit # message's source. The hook's purpose is to edit the commit # message file. If the hook fails with a non-zero status, # the commit is aborted. # # To enable this hook, rename this file to "prepare-commit-msg". # This hook includes three examples. The first comments out the # "Conflicts:" part of a merge commit. # # The second includes the output of "git diff --name-status -r" # into the message, just before the "git status" output. It is # commented because it doesn't cope with --amend or with squashed # commits. # # The third example adds a Signed-off-by line to the message, that can # still be edited. This is rarely a good idea. case "$2,$3" in merge,) /usr/bin/perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;; # ,|template,) # /usr/bin/perl -i.bak -pe ' # print "\n" . `git diff --cached --name-status -r` # if /^#/ && $first++ == 0' "$1" ;; *) ;; esac # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" ================================================ FILE: levels/blame/.githug/hooks/update.sample ================================================ #!/bin/sh # # An example hook script to blocks unannotated tags from entering. # Called by "git receive-pack" with arguments: refname sha1-old sha1-new # # To enable this hook, rename this file to "update". # # Config # ------ # hooks.allowunannotated # This boolean sets whether unannotated tags will be allowed into the # repository. By default they won't be. # hooks.allowdeletetag # This boolean sets whether deleting tags will be allowed in the # repository. By default they won't be. # hooks.allowmodifytag # This boolean sets whether a tag may be modified after creation. By default # it won't be. # hooks.allowdeletebranch # This boolean sets whether deleting branches will be allowed in the # repository. By default they won't be. # hooks.denycreatebranch # This boolean sets whether remotely creating branches will be denied # in the repository. By default this is allowed. # # --- Command line refname="$1" oldrev="$2" newrev="$3" # --- Safety check if [ -z "$GIT_DIR" ]; then echo "Don't run this script from the command line." >&2 echo " (if you want, you could supply GIT_DIR then run" >&2 echo " $0 )" >&2 exit 1 fi if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then echo "Usage: $0 " >&2 exit 1 fi # --- Config allowunannotated=$(git config --bool hooks.allowunannotated) allowdeletebranch=$(git config --bool hooks.allowdeletebranch) denycreatebranch=$(git config --bool hooks.denycreatebranch) allowdeletetag=$(git config --bool hooks.allowdeletetag) allowmodifytag=$(git config --bool hooks.allowmodifytag) # check for no description projectdesc=$(sed -e '1q' "$GIT_DIR/description") case "$projectdesc" in "Unnamed repository"* | "") echo "*** Project description file hasn't been set" >&2 exit 1 ;; esac # --- Check types # if $newrev is 0000...0000, it's a commit to delete a ref. zero="0000000000000000000000000000000000000000" if [ "$newrev" = "$zero" ]; then newrev_type=delete else newrev_type=$(git cat-file -t $newrev) fi case "$refname","$newrev_type" in refs/tags/*,commit) # un-annotated tag short_refname=${refname##refs/tags/} if [ "$allowunannotated" != "true" ]; then echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2 echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 exit 1 fi ;; refs/tags/*,delete) # delete tag if [ "$allowdeletetag" != "true" ]; then echo "*** Deleting a tag is not allowed in this repository" >&2 exit 1 fi ;; refs/tags/*,tag) # annotated tag if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 then echo "*** Tag '$refname' already exists." >&2 echo "*** Modifying a tag is not allowed in this repository." >&2 exit 1 fi ;; refs/heads/*,commit) # branch if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then echo "*** Creating a branch is not allowed in this repository" >&2 exit 1 fi ;; refs/heads/*,delete) # delete branch if [ "$allowdeletebranch" != "true" ]; then echo "*** Deleting a branch is not allowed in this repository" >&2 exit 1 fi ;; refs/remotes/*,commit) # tracking branch ;; refs/remotes/*,delete) # delete tracking branch if [ "$allowdeletebranch" != "true" ]; then echo "*** Deleting a tracking branch is not allowed in this repository" >&2 exit 1 fi ;; *) # Anything else (is there anything else?) echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 exit 1 ;; esac # --- Finished exit 0 ================================================ FILE: levels/blame/.githug/info/exclude ================================================ # git ls-files --others --exclude-from=.git/info/exclude # Lines that start with '#' are comments. # For a project mostly in C, the following would be a good set of # exclude patterns (uncomment them if you want to use them): # *.[oa] # *~ ================================================ FILE: levels/blame/.githug/logs/HEAD ================================================ 0000000000000000000000000000000000000000 5e8863df752e3b7f2150df7c78f12bef6f1ff00e Gary Rennie 1331247924 +0000 commit (initial): added config with name 5e8863df752e3b7f2150df7c78f12bef6f1ff00e 094094808dc6dc336c93c8602190a9e5f7bd6a11 Spider Man 1331247978 +0000 commit: added options 094094808dc6dc336c93c8602190a9e5f7bd6a11 70d00535a3a25b0ac1736dd3d306d6271e5427ed Bruce Banner 1331248061 +0000 commit: added password 70d00535a3a25b0ac1736dd3d306d6271e5427ed 97bdd0cccf9f4b8730f78cb53a81a74f205dbcc2 Spider Man 1331248095 +0000 commit: added more options 97bdd0cccf9f4b8730f78cb53a81a74f205dbcc2 ffd39c2dbfd94bdbca06d48686e0cbda642f3de7 Gary Rennie 1331248138 +0000 commit: added more options (no really) ================================================ FILE: levels/blame/.githug/logs/refs/heads/master ================================================ 0000000000000000000000000000000000000000 5e8863df752e3b7f2150df7c78f12bef6f1ff00e Gary Rennie 1331247924 +0000 commit (initial): added config with name 5e8863df752e3b7f2150df7c78f12bef6f1ff00e 094094808dc6dc336c93c8602190a9e5f7bd6a11 Spider Man 1331247978 +0000 commit: added options 094094808dc6dc336c93c8602190a9e5f7bd6a11 70d00535a3a25b0ac1736dd3d306d6271e5427ed Bruce Banner 1331248061 +0000 commit: added password 70d00535a3a25b0ac1736dd3d306d6271e5427ed 97bdd0cccf9f4b8730f78cb53a81a74f205dbcc2 Spider Man 1331248095 +0000 commit: added more options 97bdd0cccf9f4b8730f78cb53a81a74f205dbcc2 ffd39c2dbfd94bdbca06d48686e0cbda642f3de7 Gary Rennie 1331248138 +0000 commit: added more options (no really) ================================================ FILE: levels/blame/.githug/objects/09/4094808dc6dc336c93c8602190a9e5f7bd6a11 ================================================ xNK1tS^/dN2$qW.*majjK6褧HDа 0UVGR>]t\, "Α {N \WOK9yuPJH'L1_m`!%LPkdM ================================================ FILE: levels/blame/.githug/objects/31/11dda1f5b08d50ac44b99acabfa54f1e6e72b0 ================================================ x5 1 D=MAD/l7@MdSݶ&CzEH䎋i;6SzrGJJu[+oSJV@쭁o-e[#3 ================================================ FILE: levels/blame/.githug/objects/97/bdd0cccf9f4b8730f78cb53a81a74f205dbcc2 ================================================ xKj1DS$t`L.UNi( >((-ulͩ7U`"'!Fo{1p4uL? )Uܴt(θl2Bx#xr6_CZϡϥnW cm<@pc߇)VBZ1nK ================================================ FILE: levels/blame/.githug/objects/dd/df1d8ebd60eec169c15a5b23cb49a58d2ed5a0 ================================================ xeA 0E]cW Ņ.Ģ x ITLIw7I(]$T+X7isp`YHc %.lӲUP$( >v@h # k$}c>qcAە.RqKR8}JgbH)դxkZIэ ?X ================================================ FILE: levels/blame/.githug/objects/ff/d39c2dbfd94bdbca06d48686e0cbda642f3de7 ================================================ xMJD1]t$t`ܹNG$C&"Ž}ix |ڠ!搲ٸ1Ґ6!.53X|t is/Vo91[E?qOim8Jt2޿ʽ^8gG^Zfيr ~{o7xnqOdbO[ ================================================ FILE: levels/blame/.githug/refs/heads/master ================================================ ffd39c2dbfd94bdbca06d48686e0cbda642f3de7 ================================================ FILE: levels/blame/config.rb ================================================ class Config attr_accessor :name, :password def initialize(name, password = nil, options = {}) @name = name @password = password || "i<3evil" if options[:downcase] @name.downcase! end if options[:upcase] @name.upcase! end end end ================================================ FILE: levels/blame.rb ================================================ difficulty 2 description "Identify who put a password inside the file `config.rb`." setup do init_from_level system "git branch -m master" end solution do offender = repo.commit("97bdd0cccf9f4b8730f78cb53a81a74f205dbcc2").author.name request("Who made the commit with the password?").downcase.strip == offender.downcase end hint do puts "You want to research the `git blame` command." end ================================================ FILE: levels/branch.rb ================================================ difficulty 1 description "To work on a piece of code that has the potential to break things, create the branch test_code." setup do repo.init FileUtils.touch("README") repo.add "README" repo.commit_all("Initial commit") system "git branch -m master" end solution do repo.branches.map(&:name).include?("test_code") end hint do puts "`git branch` is what you want to investigate." end ================================================ FILE: levels/branch_at.rb ================================================ difficulty 3 description "You forgot to branch at the previous commit and made a commit on top of it. Create the branch test_branch at the commit before the last." setup do repo.init FileUtils.touch("file1") repo.add("file1") repo.commit_all("Adding file1") File.open("file1", 'w') { |f| f.write("content") } repo.add("file1") repo.commit_all("Updating file1") File.open("file1", 'a') { |f| f.write("\nAdding some more text") } repo.add("file1") repo.commit_all("Updating file1 again") system "git branch -m master" end solution do return false unless repo.branches.map(&:name).include?("test_branch") repo.commits("test_branch").each { |commit| return false if commit.message == "Updating file1 again" } true end hint do puts "Just like creating a branch, but you have to pass an extra argument." end ================================================ FILE: levels/checkout.rb ================================================ difficulty 2 description "Create and switch to a new branch called my_branch. You will need to create a branch like you did in the previous level." setup do repo.init FileUtils.touch("README") repo.add("README") repo.commit_all("initial commit") system "git branch -m master" end solution do return false unless repo.head.name == "my_branch" true end hint do puts "Try looking up `git checkout` and `git branch`." end ================================================ FILE: levels/checkout_file.rb ================================================ difficulty 3 description "A file has been modified, but you don't want to keep the modification. Checkout the `config.rb` file from the last commit." setup do repo.init File.open("config.rb", "w") do |file| file.puts("This is the initial config file") end repo.add("config.rb") repo.commit_all("Added initial config file") File.open("config.rb", "a") do |file| file.puts("These are changed you don't want to keep!") end system "git branch -m master" end solution do repo.status.files["config.rb"].type != "M" && repo.commits.length == 1 end hint do puts "You will need to do some research on the checkout command for this one." end ================================================ FILE: levels/checkout_tag.rb ================================================ difficulty 2 description "You need to fix a bug in the version 1.2 of your app. Checkout the tag `v1.2`." setup do repo.init FileUtils.touch("app.rb") repo.add("app.rb") repo.commit_all("Initial commit") `echo "Some code" >> app.rb` repo.add("app.rb") repo.commit_all("Some changes") repo.git.tag( { 'f' => true }, "v1.0" ) `echo "Buggy code" >> app.rb` repo.add("app.rb") repo.commit_all("Some more changes") repo.git.tag( { 'f' => true }, "v1.2" ) `echo "More code" >> app.rb` repo.add("app.rb") repo.commit_all("Yet more changes") `echo "Some more code" >> app.rb` repo.add("app.rb") repo.commit_all("Changes galore") repo.git.tag( { 'f' => true }, "v1.5" ) system "git branch -m master" end solution do return false unless repo.commits.length == 5 return false unless `git show HEAD --format=%s` =~ /Some more changes/ true end hint do puts "There's no big difference between checking out a branch and checking out a tag." end ================================================ FILE: levels/checkout_tag_over_branch.rb ================================================ difficulty 2 description "You need to fix a bug in the version 1.2 of your app. Checkout the tag `v1.2` (Note: There is also a branch named `v1.2`)." setup do repo.init FileUtils.touch("app.rb") repo.add("app.rb") repo.commit_all("Initial commit") system "git branch -m master" `echo "Some code" >> app.rb` repo.add("app.rb") repo.commit_all("Some changes") repo.git.tag( { 'f' => true }, "v1.0" ) `echo "Buggy code" >> app.rb` repo.add("app.rb") repo.commit_all("Some more changes") repo.git.tag( { 'f' => true }, "v1.2" ) `echo "More code" >> app.rb` repo.add("app.rb") repo.commit_all("Yet more changes") `echo "Some more code" >> app.rb` repo.add("app.rb") repo.commit_all("Changes galore") repo.git.tag( { 'f' => true }, "v1.5" ) repo.git.native :checkout, {"b" => true}, 'v1.2' File.open("file3", 'w') { |f| f << "some feature\n" } repo.add "file3" repo.commit_all "Developing new features" repo.git.native :checkout, {}, 'master' end solution do return false unless repo.commits.length == 5 return false unless `git show HEAD --format=%s` =~ /Some more changes/ true end hint do puts "You should think about specifying you're after the tag named `v1.2` (think `tags/`)." end ================================================ FILE: levels/cherry-pick/.githug/COMMIT_EDITMSG ================================================ some small fixes ================================================ FILE: levels/cherry-pick/.githug/HEAD ================================================ ref: refs/heads/master ================================================ FILE: levels/cherry-pick/.githug/config ================================================ [core] repositoryformatversion = 0 filemode = true bare = false logallrefupdates = true ignorecase = true ================================================ FILE: levels/cherry-pick/.githug/description ================================================ Unnamed repository; edit this file 'description' to name the repository. ================================================ FILE: levels/cherry-pick/.githug/hooks/applypatch-msg.sample ================================================ #!/bin/sh # # An example hook script to check the commit log message taken by # applypatch from an e-mail message. # # The hook should exit with non-zero status after issuing an # appropriate message if it wants to stop the commit. The hook is # allowed to edit the commit message file. # # To enable this hook, rename this file to "applypatch-msg". . git-sh-setup test -x "$GIT_DIR/hooks/commit-msg" && exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"} : ================================================ FILE: levels/cherry-pick/.githug/hooks/commit-msg.sample ================================================ #!/bin/sh # # An example hook script to check the commit log message. # Called by "git commit" with one argument, the name of the file # that has the commit message. The hook should exit with non-zero # status after issuing an appropriate message if it wants to stop the # commit. The hook is allowed to edit the commit message file. # # To enable this hook, rename this file to "commit-msg". # Uncomment the below to add a Signed-off-by line to the message. # Doing this in a hook is a bad idea in general, but the prepare-commit-msg # hook is more suited to it. # # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" # This example catches duplicate Signed-off-by lines. test "" = "$(grep '^Signed-off-by: ' "$1" | sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { echo >&2 Duplicate Signed-off-by lines. exit 1 } ================================================ FILE: levels/cherry-pick/.githug/hooks/post-update.sample ================================================ #!/bin/sh # # An example hook script to prepare a packed repository for use over # dumb transports. # # To enable this hook, rename this file to "post-update". exec git update-server-info ================================================ FILE: levels/cherry-pick/.githug/hooks/pre-applypatch.sample ================================================ #!/bin/sh # # An example hook script to verify what is about to be committed # by applypatch from an e-mail message. # # The hook should exit with non-zero status after issuing an # appropriate message if it wants to stop the commit. # # To enable this hook, rename this file to "pre-applypatch". . git-sh-setup test -x "$GIT_DIR/hooks/pre-commit" && exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"} : ================================================ FILE: levels/cherry-pick/.githug/hooks/pre-commit.sample ================================================ #!/bin/sh # # An example hook script to verify what is about to be committed. # Called by "git commit" with no arguments. The hook should # exit with non-zero status after issuing an appropriate message if # it wants to stop the commit. # # To enable this hook, rename this file to "pre-commit". if git rev-parse --verify HEAD >/dev/null 2>&1 then against=HEAD else # Initial commit: diff against an empty tree object against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 fi # If you want to allow non-ascii filenames set this variable to true. allownonascii=$(git config hooks.allownonascii) # Redirect output to stderr. exec 1>&2 # Cross platform projects tend to avoid non-ascii filenames; prevent # them from being added to the repository. We exploit the fact that the # printable range starts at the space character and ends with tilde. if [ "$allownonascii" != "true" ] && # Note that the use of brackets around a tr range is ok here, (it's # even required, for portability to Solaris 10's /usr/bin/tr), since # the square bracket bytes happen to fall in the designated range. test $(git diff --cached --name-only --diff-filter=A -z $against | LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 then echo "Error: Attempt to add a non-ascii file name." echo echo "This can cause problems if you want to work" echo "with people on other platforms." echo echo "To be portable it is advisable to rename the file ..." echo echo "If you know what you are doing you can disable this" echo "check using:" echo echo " git config hooks.allownonascii true" echo exit 1 fi # If there are whitespace errors, print the offending file names and fail. exec git diff-index --check --cached $against -- ================================================ FILE: levels/cherry-pick/.githug/hooks/pre-rebase.sample ================================================ #!/bin/sh # # Copyright (c) 2006, 2008 Junio C Hamano # # The "pre-rebase" hook is run just before "git rebase" starts doing # its job, and can prevent the command from running by exiting with # non-zero status. # # The hook is called with the following parameters: # # $1 -- the upstream the series was forked from. # $2 -- the branch being rebased (or empty when rebasing the current branch). # # This sample shows how to prevent topic branches that are already # merged to 'next' branch from getting rebased, because allowing it # would result in rebasing already published history. publish=next basebranch="$1" if test "$#" = 2 then topic="refs/heads/$2" else topic=`git symbolic-ref HEAD` || exit 0 ;# we do not interrupt rebasing detached HEAD fi case "$topic" in refs/heads/??/*) ;; *) exit 0 ;# we do not interrupt others. ;; esac # Now we are dealing with a topic branch being rebased # on top of master. Is it OK to rebase it? # Does the topic really exist? git show-ref -q "$topic" || { echo >&2 "No such branch $topic" exit 1 } # Is topic fully merged to master? not_in_master=`git rev-list --pretty=oneline ^master "$topic"` if test -z "$not_in_master" then echo >&2 "$topic is fully merged to master; better remove it." exit 1 ;# we could allow it, but there is no point. fi # Is topic ever merged to next? If so you should not be rebasing it. only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` only_next_2=`git rev-list ^master ${publish} | sort` if test "$only_next_1" = "$only_next_2" then not_in_topic=`git rev-list "^$topic" master` if test -z "$not_in_topic" then echo >&2 "$topic is already up-to-date with master" exit 1 ;# we could allow it, but there is no point. else exit 0 fi else not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` /opt/local/bin/perl5.12 -e ' my $topic = $ARGV[0]; my $msg = "* $topic has commits already merged to public branch:\n"; my (%not_in_next) = map { /^([0-9a-f]+) /; ($1 => 1); } split(/\n/, $ARGV[1]); for my $elem (map { /^([0-9a-f]+) (.*)$/; [$1 => $2]; } split(/\n/, $ARGV[2])) { if (!exists $not_in_next{$elem->[0]}) { if ($msg) { print STDERR $msg; undef $msg; } print STDERR " $elem->[1]\n"; } } ' "$topic" "$not_in_next" "$not_in_master" exit 1 fi exit 0 ################################################################ This sample hook safeguards topic branches that have been published from being rewound. The workflow assumed here is: * Once a topic branch forks from "master", "master" is never merged into it again (either directly or indirectly). * Once a topic branch is fully cooked and merged into "master", it is deleted. If you need to build on top of it to correct earlier mistakes, a new topic branch is created by forking at the tip of the "master". This is not strictly necessary, but it makes it easier to keep your history simple. * Whenever you need to test or publish your changes to topic branches, merge them into "next" branch. The script, being an example, hardcodes the publish branch name to be "next", but it is trivial to make it configurable via $GIT_DIR/config mechanism. With this workflow, you would want to know: (1) ... if a topic branch has ever been merged to "next". Young topic branches can have stupid mistakes you would rather clean up before publishing, and things that have not been merged into other branches can be easily rebased without affecting other people. But once it is published, you would not want to rewind it. (2) ... if a topic branch has been fully merged to "master". Then you can delete it. More importantly, you should not build on top of it -- other people may already want to change things related to the topic as patches against your "master", so if you need further changes, it is better to fork the topic (perhaps with the same name) afresh from the tip of "master". Let's look at this example: o---o---o---o---o---o---o---o---o---o "next" / / / / / a---a---b A / / / / / / / / c---c---c---c B / / / / \ / / / / b---b C \ / / / / / \ / ---o---o---o---o---o---o---o---o---o---o---o "master" A, B and C are topic branches. * A has one fix since it was merged up to "next". * B has finished. It has been fully merged up to "master" and "next", and is ready to be deleted. * C has not merged to "next" at all. We would want to allow C to be rebased, refuse A, and encourage B to be deleted. To compute (1): git rev-list ^master ^topic next git rev-list ^master next if these match, topic has not merged in next at all. To compute (2): git rev-list master..topic if this is empty, it is fully merged to "master". ================================================ FILE: levels/cherry-pick/.githug/hooks/prepare-commit-msg.sample ================================================ #!/bin/sh # # An example hook script to prepare the commit log message. # Called by "git commit" with the name of the file that has the # commit message, followed by the description of the commit # message's source. The hook's purpose is to edit the commit # message file. If the hook fails with a non-zero status, # the commit is aborted. # # To enable this hook, rename this file to "prepare-commit-msg". # This hook includes three examples. The first comments out the # "Conflicts:" part of a merge commit. # # The second includes the output of "git diff --name-status -r" # into the message, just before the "git status" output. It is # commented because it doesn't cope with --amend or with squashed # commits. # # The third example adds a Signed-off-by line to the message, that can # still be edited. This is rarely a good idea. case "$2,$3" in merge,) /opt/local/bin/perl5.12 -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;; # ,|template,) # /opt/local/bin/perl5.12 -i.bak -pe ' # print "\n" . `git diff --cached --name-status -r` # if /^#/ && $first++ == 0' "$1" ;; *) ;; esac # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" ================================================ FILE: levels/cherry-pick/.githug/hooks/update.sample ================================================ #!/bin/sh # # An example hook script to blocks unannotated tags from entering. # Called by "git receive-pack" with arguments: refname sha1-old sha1-new # # To enable this hook, rename this file to "update". # # Config # ------ # hooks.allowunannotated # This boolean sets whether unannotated tags will be allowed into the # repository. By default they won't be. # hooks.allowdeletetag # This boolean sets whether deleting tags will be allowed in the # repository. By default they won't be. # hooks.allowmodifytag # This boolean sets whether a tag may be modified after creation. By default # it won't be. # hooks.allowdeletebranch # This boolean sets whether deleting branches will be allowed in the # repository. By default they won't be. # hooks.denycreatebranch # This boolean sets whether remotely creating branches will be denied # in the repository. By default this is allowed. # # --- Command line refname="$1" oldrev="$2" newrev="$3" # --- Safety check if [ -z "$GIT_DIR" ]; then echo "Don't run this script from the command line." >&2 echo " (if you want, you could supply GIT_DIR then run" >&2 echo " $0 )" >&2 exit 1 fi if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then echo "Usage: $0 " >&2 exit 1 fi # --- Config allowunannotated=$(git config --bool hooks.allowunannotated) allowdeletebranch=$(git config --bool hooks.allowdeletebranch) denycreatebranch=$(git config --bool hooks.denycreatebranch) allowdeletetag=$(git config --bool hooks.allowdeletetag) allowmodifytag=$(git config --bool hooks.allowmodifytag) # check for no description projectdesc=$(sed -e '1q' "$GIT_DIR/description") case "$projectdesc" in "Unnamed repository"* | "") echo "*** Project description file hasn't been set" >&2 exit 1 ;; esac # --- Check types # if $newrev is 0000...0000, it's a commit to delete a ref. zero="0000000000000000000000000000000000000000" if [ "$newrev" = "$zero" ]; then newrev_type=delete else newrev_type=$(git cat-file -t $newrev) fi case "$refname","$newrev_type" in refs/tags/*,commit) # un-annotated tag short_refname=${refname##refs/tags/} if [ "$allowunannotated" != "true" ]; then echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2 echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 exit 1 fi ;; refs/tags/*,delete) # delete tag if [ "$allowdeletetag" != "true" ]; then echo "*** Deleting a tag is not allowed in this repository" >&2 exit 1 fi ;; refs/tags/*,tag) # annotated tag if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 then echo "*** Tag '$refname' already exists." >&2 echo "*** Modifying a tag is not allowed in this repository." >&2 exit 1 fi ;; refs/heads/*,commit) # branch if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then echo "*** Creating a branch is not allowed in this repository" >&2 exit 1 fi ;; refs/heads/*,delete) # delete branch if [ "$allowdeletebranch" != "true" ]; then echo "*** Deleting a branch is not allowed in this repository" >&2 exit 1 fi ;; refs/remotes/*,commit) # tracking branch ;; refs/remotes/*,delete) # delete tracking branch if [ "$allowdeletebranch" != "true" ]; then echo "*** Deleting a tracking branch is not allowed in this repository" >&2 exit 1 fi ;; *) # Anything else (is there anything else?) echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 exit 1 ;; esac # --- Finished exit 0 ================================================ FILE: levels/cherry-pick/.githug/info/exclude ================================================ # git ls-files --others --exclude-from=.git/info/exclude # Lines that start with '#' are comments. # For a project mostly in C, the following would be a good set of # exclude patterns (uncomment them if you want to use them): # *.[oa] # *~ ================================================ FILE: levels/cherry-pick/.githug/logs/HEAD ================================================ 0000000000000000000000000000000000000000 ea3dbcc5e2d2359698c3606b0ec44af9f76def54 Andrey 1332886832 +0400 commit (initial): Initial commit ea3dbcc5e2d2359698c3606b0ec44af9f76def54 b30c6a965415df6aef5f2903f9892fa5481152fc Andrey 1332886882 +0400 commit: Added a hardcore math module b30c6a965415df6aef5f2903f9892fa5481152fc 232d266a78d5ef7196f1ede14972ccf7ee19e587 Andrey 1332886949 +0400 commit: Renamed project.js -> herdcore-math.js 232d266a78d5ef7196f1ede14972ccf7ee19e587 6edea632d9540e060bca97dda0897df2b7da0ec0 Andrey 1332887008 +0400 commit: Added fancy branded output 6edea632d9540e060bca97dda0897df2b7da0ec0 ea3dbcc5e2d2359698c3606b0ec44af9f76def54 Andrey 1332887015 +0400 checkout: moving from master to ea3dbcc ea3dbcc5e2d2359698c3606b0ec44af9f76def54 ea3dbcc5e2d2359698c3606b0ec44af9f76def54 Andrey 1332887030 +0400 checkout: moving from ea3dbcc5e2d2359698c3606b0ec44af9f76def54 to new-feature ea3dbcc5e2d2359698c3606b0ec44af9f76def54 58a8c8edcfdd00c6d8cce9aada8f987a1677571f Andrey 1332887081 +0400 commit: Added a stub for the feature 58a8c8edcfdd00c6d8cce9aada8f987a1677571f cfd8ce38c22c5fe83cc04e23f94646464f20d990 Andrey 1332887151 +0400 commit: README.md cfd8ce38c22c5fe83cc04e23f94646464f20d990 ca32a6dac7b6f97975edbe19a4296c2ee7682f68 Andrey 1332887177 +0400 commit (amend): Filled in README.md with proper input ca32a6dac7b6f97975edbe19a4296c2ee7682f68 4a1961bce62840eaef9c4392fe5cc799e38c9b7b Andrey 1332887238 +0400 commit: Fixed feature 4a1961bce62840eaef9c4392fe5cc799e38c9b7b ea2a47c19b85fc321e2737ddc49db3deeba3a1b5 Andrey 1332887315 +0400 commit: some small fixes ea2a47c19b85fc321e2737ddc49db3deeba3a1b5 6edea632d9540e060bca97dda0897df2b7da0ec0 Andrey 1332887329 +0400 checkout: moving from new-feature to master 6edea632d9540e060bca97dda0897df2b7da0ec0 ea2a47c19b85fc321e2737ddc49db3deeba3a1b5 Andrey 1332887695 +0400 checkout: moving from master to new-feature ea2a47c19b85fc321e2737ddc49db3deeba3a1b5 6edea632d9540e060bca97dda0897df2b7da0ec0 Andrey 1332887737 +0400 checkout: moving from new-feature to master ================================================ FILE: levels/cherry-pick/.githug/logs/refs/heads/master ================================================ 0000000000000000000000000000000000000000 ea3dbcc5e2d2359698c3606b0ec44af9f76def54 Andrey 1332886832 +0400 commit (initial): Initial commit ea3dbcc5e2d2359698c3606b0ec44af9f76def54 b30c6a965415df6aef5f2903f9892fa5481152fc Andrey 1332886882 +0400 commit: Added a hardcore math module b30c6a965415df6aef5f2903f9892fa5481152fc 232d266a78d5ef7196f1ede14972ccf7ee19e587 Andrey 1332886949 +0400 commit: Renamed project.js -> herdcore-math.js 232d266a78d5ef7196f1ede14972ccf7ee19e587 6edea632d9540e060bca97dda0897df2b7da0ec0 Andrey 1332887008 +0400 commit: Added fancy branded output ================================================ FILE: levels/cherry-pick/.githug/logs/refs/heads/new-feature ================================================ 0000000000000000000000000000000000000000 ea3dbcc5e2d2359698c3606b0ec44af9f76def54 Andrey 1332887030 +0400 branch: Created from HEAD ea3dbcc5e2d2359698c3606b0ec44af9f76def54 58a8c8edcfdd00c6d8cce9aada8f987a1677571f Andrey 1332887081 +0400 commit: Added a stub for the feature 58a8c8edcfdd00c6d8cce9aada8f987a1677571f cfd8ce38c22c5fe83cc04e23f94646464f20d990 Andrey 1332887151 +0400 commit: README.md cfd8ce38c22c5fe83cc04e23f94646464f20d990 ca32a6dac7b6f97975edbe19a4296c2ee7682f68 Andrey 1332887177 +0400 commit (amend): Filled in README.md with proper input ca32a6dac7b6f97975edbe19a4296c2ee7682f68 4a1961bce62840eaef9c4392fe5cc799e38c9b7b Andrey 1332887238 +0400 commit: Fixed feature 4a1961bce62840eaef9c4392fe5cc799e38c9b7b ea2a47c19b85fc321e2737ddc49db3deeba3a1b5 Andrey 1332887315 +0400 commit: some small fixes ================================================ FILE: levels/cherry-pick/.githug/objects/6e/dea632d9540e060bca97dda0897df2b7da0ec0 ================================================ xA E]s &{ тAjy/,ܤ̩Uf 쉌Kj`-ʙ^C|N&`bnƄ8amSrTyy)?0.,7 ]C<^)_K11rXC>VY{m[K( ================================================ FILE: levels/cherry-pick/.githug/refs/heads/master ================================================ 6edea632d9540e060bca97dda0897df2b7da0ec0 ================================================ FILE: levels/cherry-pick/.githug/refs/heads/new-feature ================================================ ea2a47c19b85fc321e2737ddc49db3deeba3a1b5 ================================================ FILE: levels/cherry-pick/README.md ================================================ I'll fill in the file some time later.. ================================================ FILE: levels/cherry-pick/hardcore-math.js ================================================ for(var i = 0; i < 10; i++) { console.log(42 * i); } ================================================ FILE: levels/cherry-pick/nokia.js ================================================ console.log("[NOKIA] Connecting people"); ================================================ FILE: levels/cherry-pick.rb ================================================ difficulty 3 description "Your new feature isn't worth the time and you're going to delete it. But it has one commit that fills in `README` file, and you want this commit to be on the master as well." setup do init_from_level `git stash` # fix for README.md being in githug root an the level system "git branch -m master" end solution do return false unless repo.commits[1].message == "Added fancy branded output" return false unless repo.commits[0].message == "Filled in README.md with proper input" true end hint do puts "Sneak a peek at the `git help cherry-pick` command." end ================================================ FILE: levels/clone.rb ================================================ difficulty 1 description "Clone the repository at https://github.com/Gazler/cloneme." solution do repo("cloneme").commit("157b2b61f29ab9df45f31c7cd9cb5d8ff06ecde4") end hint do puts "You should have a look at this site: https://github.com/Gazler/cloneme." end ================================================ FILE: levels/clone_to_folder.rb ================================================ difficulty 1 description "Clone the repository at https://github.com/Gazler/cloneme into the folder `my_cloned_repo`." solution do repo("my_cloned_repo").commit("157b2b61f29ab9df45f31c7cd9cb5d8ff06ecde4") end hint do puts "This is like the last level, `git clone` has an optional argument." end ================================================ FILE: levels/commit.rb ================================================ difficulty 1 description "The `README` file has been added to your staging area, now commit it." setup do repo.init system "git branch -m master" FileUtils.touch("README") repo.add("README") end solution do return false if repo.commits.empty? true end hint do puts "You must include a message when you commit." end ================================================ FILE: levels/commit_amend.rb ================================================ difficulty 2 description "The `README` file has been committed, but it looks like the file `forgotten_file.rb` was missing from the commit. Add the file and amend your previous commit to include it." setup do repo.init FileUtils.touch("README") repo.add("README") repo.commit_all("Initial commit") FileUtils.touch("forgotten_file.rb") system "git branch -m master" end solution do # Reset config - see issue #74 file = File.open(".git/config", "w") do |file| file.puts("[format]") file.puts(" pretty = medium") end repo.commits.length == 1 && Grit::CommitStats.find_all(repo, repo.commits.first.sha).first[1].files.length == 2 end hint do puts "Running `git commit --help` will display the man page and possible flags." end ================================================ FILE: levels/commit_in_future.rb ================================================ require 'time' difficulty 2 description "Commit your changes with the future date (e.g. tomorrow)." setup do repo.init FileUtils.touch("README") repo.add("README") system "git branch -m master" end solution do repo.commits.length == 1 && repo.commits.first.authored_date > Time.now end hint do puts "Build a time format, and commit your code using --date parameter for \"future\"." end ================================================ FILE: levels/config.rb ================================================ difficulty 1 description "Set up your git name and email; this is important so that your commits can be identified." setup do repo.init end solution do valid = false name = request("What is your name?") email = request("What is your email?") config_name = repo.config["user.name"] config_email = repo.config["user.email"] if name.respond_to?(:force_encoding) config_name = config_name.force_encoding("UTF-8") config_email = config_email.force_encoding("UTF-8") end if name == config_name && email == config_email valid = true end puts "Your config has the following name: #{config_name}" puts "Your config has the following email: #{config_email}" valid end hint do puts "These settings are config settings. You should run `git help config` if you are stuck." end ================================================ FILE: levels/conflict/.githug/COMMIT_EDITMSG ================================================ Updated the poem # Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # # Author: Thameera Senanayaka # Committer: Alex # # On branch master # Changes to be committed: # (use "git reset HEAD^1 ..." to unstage) # # modified: poem.txt # ================================================ FILE: levels/conflict/.githug/HEAD ================================================ ref: refs/heads/master ================================================ FILE: levels/conflict/.githug/config ================================================ [core] repositoryformatversion = 0 filemode = true bare = false logallrefupdates = true ================================================ FILE: levels/conflict/.githug/description ================================================ Unnamed repository; edit this file 'description' to name the repository. ================================================ FILE: levels/conflict/.githug/hooks/applypatch-msg.sample ================================================ #!/bin/sh # # An example hook script to check the commit log message taken by # applypatch from an e-mail message. # # The hook should exit with non-zero status after issuing an # appropriate message if it wants to stop the commit. The hook is # allowed to edit the commit message file. # # To enable this hook, rename this file to "applypatch-msg". . git-sh-setup test -x "$GIT_DIR/hooks/commit-msg" && exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"} : ================================================ FILE: levels/conflict/.githug/hooks/commit-msg.sample ================================================ #!/bin/sh # # An example hook script to check the commit log message. # Called by "git commit" with one argument, the name of the file # that has the commit message. The hook should exit with non-zero # status after issuing an appropriate message if it wants to stop the # commit. The hook is allowed to edit the commit message file. # # To enable this hook, rename this file to "commit-msg". # Uncomment the below to add a Signed-off-by line to the message. # Doing this in a hook is a bad idea in general, but the prepare-commit-msg # hook is more suited to it. # # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" # This example catches duplicate Signed-off-by lines. test "" = "$(grep '^Signed-off-by: ' "$1" | sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { echo >&2 Duplicate Signed-off-by lines. exit 1 } ================================================ FILE: levels/conflict/.githug/hooks/post-update.sample ================================================ #!/bin/sh # # An example hook script to prepare a packed repository for use over # dumb transports. # # To enable this hook, rename this file to "post-update". exec git update-server-info ================================================ FILE: levels/conflict/.githug/hooks/pre-applypatch.sample ================================================ #!/bin/sh # # An example hook script to verify what is about to be committed # by applypatch from an e-mail message. # # The hook should exit with non-zero status after issuing an # appropriate message if it wants to stop the commit. # # To enable this hook, rename this file to "pre-applypatch". . git-sh-setup test -x "$GIT_DIR/hooks/pre-commit" && exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"} : ================================================ FILE: levels/conflict/.githug/hooks/pre-commit.sample ================================================ #!/bin/sh # # An example hook script to verify what is about to be committed. # Called by "git commit" with no arguments. The hook should # exit with non-zero status after issuing an appropriate message if # it wants to stop the commit. # # To enable this hook, rename this file to "pre-commit". if git rev-parse --verify HEAD >/dev/null 2>&1 then against=HEAD else # Initial commit: diff against an empty tree object against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 fi # If you want to allow non-ascii filenames set this variable to true. allownonascii=$(git config hooks.allownonascii) # Redirect output to stderr. exec 1>&2 # Cross platform projects tend to avoid non-ascii filenames; prevent # them from being added to the repository. We exploit the fact that the # printable range starts at the space character and ends with tilde. if [ "$allownonascii" != "true" ] && # Note that the use of brackets around a tr range is ok here, (it's # even required, for portability to Solaris 10's /usr/bin/tr), since # the square bracket bytes happen to fall in the designated range. test $(git diff --cached --name-only --diff-filter=A -z $against | LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 then echo "Error: Attempt to add a non-ascii file name." echo echo "This can cause problems if you want to work" echo "with people on other platforms." echo echo "To be portable it is advisable to rename the file ..." echo echo "If you know what you are doing you can disable this" echo "check using:" echo echo " git config hooks.allownonascii true" echo exit 1 fi # If there are whitespace errors, print the offending file names and fail. exec git diff-index --check --cached $against -- ================================================ FILE: levels/conflict/.githug/hooks/pre-rebase.sample ================================================ #!/bin/sh # # Copyright (c) 2006, 2008 Junio C Hamano # # The "pre-rebase" hook is run just before "git rebase" starts doing # its job, and can prevent the command from running by exiting with # non-zero status. # # The hook is called with the following parameters: # # $1 -- the upstream the series was forked from. # $2 -- the branch being rebased (or empty when rebasing the current branch). # # This sample shows how to prevent topic branches that are already # merged to 'next' branch from getting rebased, because allowing it # would result in rebasing already published history. publish=next basebranch="$1" if test "$#" = 2 then topic="refs/heads/$2" else topic=`git symbolic-ref HEAD` || exit 0 ;# we do not interrupt rebasing detached HEAD fi case "$topic" in refs/heads/??/*) ;; *) exit 0 ;# we do not interrupt others. ;; esac # Now we are dealing with a topic branch being rebased # on top of master. Is it OK to rebase it? # Does the topic really exist? git show-ref -q "$topic" || { echo >&2 "No such branch $topic" exit 1 } # Is topic fully merged to master? not_in_master=`git rev-list --pretty=oneline ^master "$topic"` if test -z "$not_in_master" then echo >&2 "$topic is fully merged to master; better remove it." exit 1 ;# we could allow it, but there is no point. fi # Is topic ever merged to next? If so you should not be rebasing it. only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` only_next_2=`git rev-list ^master ${publish} | sort` if test "$only_next_1" = "$only_next_2" then not_in_topic=`git rev-list "^$topic" master` if test -z "$not_in_topic" then echo >&2 "$topic is already up-to-date with master" exit 1 ;# we could allow it, but there is no point. else exit 0 fi else not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` /usr/bin/perl -e ' my $topic = $ARGV[0]; my $msg = "* $topic has commits already merged to public branch:\n"; my (%not_in_next) = map { /^([0-9a-f]+) /; ($1 => 1); } split(/\n/, $ARGV[1]); for my $elem (map { /^([0-9a-f]+) (.*)$/; [$1 => $2]; } split(/\n/, $ARGV[2])) { if (!exists $not_in_next{$elem->[0]}) { if ($msg) { print STDERR $msg; undef $msg; } print STDERR " $elem->[1]\n"; } } ' "$topic" "$not_in_next" "$not_in_master" exit 1 fi <<\DOC_END This sample hook safeguards topic branches that have been published from being rewound. The workflow assumed here is: * Once a topic branch forks from "master", "master" is never merged into it again (either directly or indirectly). * Once a topic branch is fully cooked and merged into "master", it is deleted. If you need to build on top of it to correct earlier mistakes, a new topic branch is created by forking at the tip of the "master". This is not strictly necessary, but it makes it easier to keep your history simple. * Whenever you need to test or publish your changes to topic branches, merge them into "next" branch. The script, being an example, hardcodes the publish branch name to be "next", but it is trivial to make it configurable via $GIT_DIR/config mechanism. With this workflow, you would want to know: (1) ... if a topic branch has ever been merged to "next". Young topic branches can have stupid mistakes you would rather clean up before publishing, and things that have not been merged into other branches can be easily rebased without affecting other people. But once it is published, you would not want to rewind it. (2) ... if a topic branch has been fully merged to "master". Then you can delete it. More importantly, you should not build on top of it -- other people may already want to change things related to the topic as patches against your "master", so if you need further changes, it is better to fork the topic (perhaps with the same name) afresh from the tip of "master". Let's look at this example: o---o---o---o---o---o---o---o---o---o "next" / / / / / a---a---b A / / / / / / / / c---c---c---c B / / / / \ / / / / b---b C \ / / / / / \ / ---o---o---o---o---o---o---o---o---o---o---o "master" A, B and C are topic branches. * A has one fix since it was merged up to "next". * B has finished. It has been fully merged up to "master" and "next", and is ready to be deleted. * C has not merged to "next" at all. We would want to allow C to be rebased, refuse A, and encourage B to be deleted. To compute (1): git rev-list ^master ^topic next git rev-list ^master next if these match, topic has not merged in next at all. To compute (2): git rev-list master..topic if this is empty, it is fully merged to "master". DOC_END ================================================ FILE: levels/conflict/.githug/hooks/prepare-commit-msg.sample ================================================ #!/bin/sh # # An example hook script to prepare the commit log message. # Called by "git commit" with the name of the file that has the # commit message, followed by the description of the commit # message's source. The hook's purpose is to edit the commit # message file. If the hook fails with a non-zero status, # the commit is aborted. # # To enable this hook, rename this file to "prepare-commit-msg". # This hook includes three examples. The first comments out the # "Conflicts:" part of a merge commit. # # The second includes the output of "git diff --name-status -r" # into the message, just before the "git status" output. It is # commented because it doesn't cope with --amend or with squashed # commits. # # The third example adds a Signed-off-by line to the message, that can # still be edited. This is rarely a good idea. case "$2,$3" in merge,) /usr/bin/perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;; # ,|template,) # /usr/bin/perl -i.bak -pe ' # print "\n" . `git diff --cached --name-status -r` # if /^#/ && $first++ == 0' "$1" ;; *) ;; esac # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" ================================================ FILE: levels/conflict/.githug/hooks/update.sample ================================================ #!/bin/sh # # An example hook script to blocks unannotated tags from entering. # Called by "git receive-pack" with arguments: refname sha1-old sha1-new # # To enable this hook, rename this file to "update". # # Config # ------ # hooks.allowunannotated # This boolean sets whether unannotated tags will be allowed into the # repository. By default they won't be. # hooks.allowdeletetag # This boolean sets whether deleting tags will be allowed in the # repository. By default they won't be. # hooks.allowmodifytag # This boolean sets whether a tag may be modified after creation. By default # it won't be. # hooks.allowdeletebranch # This boolean sets whether deleting branches will be allowed in the # repository. By default they won't be. # hooks.denycreatebranch # This boolean sets whether remotely creating branches will be denied # in the repository. By default this is allowed. # # --- Command line refname="$1" oldrev="$2" newrev="$3" # --- Safety check if [ -z "$GIT_DIR" ]; then echo "Don't run this script from the command line." >&2 echo " (if you want, you could supply GIT_DIR then run" >&2 echo " $0 )" >&2 exit 1 fi if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then echo "Usage: $0 " >&2 exit 1 fi # --- Config allowunannotated=$(git config --bool hooks.allowunannotated) allowdeletebranch=$(git config --bool hooks.allowdeletebranch) denycreatebranch=$(git config --bool hooks.denycreatebranch) allowdeletetag=$(git config --bool hooks.allowdeletetag) allowmodifytag=$(git config --bool hooks.allowmodifytag) # check for no description projectdesc=$(sed -e '1q' "$GIT_DIR/description") case "$projectdesc" in "Unnamed repository"* | "") echo "*** Project description file hasn't been set" >&2 exit 1 ;; esac # --- Check types # if $newrev is 0000...0000, it's a commit to delete a ref. zero="0000000000000000000000000000000000000000" if [ "$newrev" = "$zero" ]; then newrev_type=delete else newrev_type=$(git cat-file -t $newrev) fi case "$refname","$newrev_type" in refs/tags/*,commit) # un-annotated tag short_refname=${refname##refs/tags/} if [ "$allowunannotated" != "true" ]; then echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2 echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 exit 1 fi ;; refs/tags/*,delete) # delete tag if [ "$allowdeletetag" != "true" ]; then echo "*** Deleting a tag is not allowed in this repository" >&2 exit 1 fi ;; refs/tags/*,tag) # annotated tag if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 then echo "*** Tag '$refname' already exists." >&2 echo "*** Modifying a tag is not allowed in this repository." >&2 exit 1 fi ;; refs/heads/*,commit) # branch if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then echo "*** Creating a branch is not allowed in this repository" >&2 exit 1 fi ;; refs/heads/*,delete) # delete branch if [ "$allowdeletebranch" != "true" ]; then echo "*** Deleting a branch is not allowed in this repository" >&2 exit 1 fi ;; refs/remotes/*,commit) # tracking branch ;; refs/remotes/*,delete) # delete tracking branch if [ "$allowdeletebranch" != "true" ]; then echo "*** Deleting a tracking branch is not allowed in this repository" >&2 exit 1 fi ;; *) # Anything else (is there anything else?) echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 exit 1 ;; esac # --- Finished exit 0 ================================================ FILE: levels/conflict/.githug/info/exclude ================================================ # git ls-files --others --exclude-from=.git/info/exclude # Lines that start with '#' are comments. # For a project mostly in C, the following would be a good set of # exclude patterns (uncomment them if you want to use them): # *.[oa] # *~ ================================================ FILE: levels/conflict/.githug/logs/HEAD ================================================ 0000000000000000000000000000000000000000 306868e3258be1f35ae43db71e3a6d7edf42ffe7 Thameera Senanayaka 1352041121 +0530 commit (initial): Initial commit 306868e3258be1f35ae43db71e3a6d7edf42ffe7 40e20a455ac2731ad25c297b03aa543d7eedf6ab Thameera Senanayaka 1352041153 +0530 commit: Added two lines 40e20a455ac2731ad25c297b03aa543d7eedf6ab 306868e3258be1f35ae43db71e3a6d7edf42ffe7 Thameera Senanayaka 1352041162 +0530 checkout: moving from master to mybranch 306868e3258be1f35ae43db71e3a6d7edf42ffe7 50a127cb066eb903a6fa59d71802c10cb442fb3b Thameera Senanayaka 1352041213 +0530 commit: Added lines 50a127cb066eb903a6fa59d71802c10cb442fb3b 75179304f4fab00613f08a9412b6cb0965bfa564 Thameera Senanayaka 1352041234 +0530 commit: Added comment 75179304f4fab00613f08a9412b6cb0965bfa564 40e20a455ac2731ad25c297b03aa543d7eedf6ab Thameera Senanayaka 1352041240 +0530 checkout: moving from mybranch to master 40e20a455ac2731ad25c297b03aa543d7eedf6ab 75179304f4fab00613f08a9412b6cb0965bfa564 Alex 1382868220 -0700 checkout: moving from master to mybranch 75179304f4fab00613f08a9412b6cb0965bfa564 bdc7bec8acae9b3eabf0a15b223a48211b7a89a1 Alex 1382868333 -0700 commit: Changed the poem bdc7bec8acae9b3eabf0a15b223a48211b7a89a1 3d7aec017559be2b61cab850dafdcb2b6212f1c3 Alex 1382868450 -0700 commit (amend): Changed the poem 3d7aec017559be2b61cab850dafdcb2b6212f1c3 40e20a455ac2731ad25c297b03aa543d7eedf6ab Alex 1382868483 -0700 checkout: moving from mybranch to master 40e20a455ac2731ad25c297b03aa543d7eedf6ab 30cc28e66966109bb5bfbe96d6c817c367d2050a Alex 1382868518 -0700 commit: Updated the poem 30cc28e66966109bb5bfbe96d6c817c367d2050a c797f979cf24ba148bf10d5e26f5d7402dd5f2e1 Alex 1382868533 -0700 commit (amend): Updated the poem ================================================ FILE: levels/conflict/.githug/logs/refs/heads/master ================================================ 0000000000000000000000000000000000000000 306868e3258be1f35ae43db71e3a6d7edf42ffe7 Thameera Senanayaka 1352041121 +0530 commit (initial): Initial commit 306868e3258be1f35ae43db71e3a6d7edf42ffe7 40e20a455ac2731ad25c297b03aa543d7eedf6ab Thameera Senanayaka 1352041153 +0530 commit: Added two lines 40e20a455ac2731ad25c297b03aa543d7eedf6ab 30cc28e66966109bb5bfbe96d6c817c367d2050a Alex 1382868518 -0700 commit: Updated the poem 30cc28e66966109bb5bfbe96d6c817c367d2050a c797f979cf24ba148bf10d5e26f5d7402dd5f2e1 Alex 1382868533 -0700 commit (amend): Updated the poem ================================================ FILE: levels/conflict/.githug/logs/refs/heads/mybranch ================================================ 0000000000000000000000000000000000000000 306868e3258be1f35ae43db71e3a6d7edf42ffe7 Thameera Senanayaka 1352041133 +0530 branch: Created from master 306868e3258be1f35ae43db71e3a6d7edf42ffe7 50a127cb066eb903a6fa59d71802c10cb442fb3b Thameera Senanayaka 1352041213 +0530 commit: Added lines 50a127cb066eb903a6fa59d71802c10cb442fb3b 75179304f4fab00613f08a9412b6cb0965bfa564 Thameera Senanayaka 1352041234 +0530 commit: Added comment 75179304f4fab00613f08a9412b6cb0965bfa564 bdc7bec8acae9b3eabf0a15b223a48211b7a89a1 Alex 1382868333 -0700 commit: Changed the poem bdc7bec8acae9b3eabf0a15b223a48211b7a89a1 3d7aec017559be2b61cab850dafdcb2b6212f1c3 Alex 1382868450 -0700 commit (amend): Changed the poem ================================================ FILE: levels/conflict/.githug/objects/2d/0d90051e320215f54f357e746c9838490557e7 ================================================ x+)JMU06c040031Q(O+(apKLyVi.G1Z> ================================================ FILE: levels/conflict/.githug/objects/3d/7aec017559be2b61cab850dafdcb2b6212f1c3 ================================================ x=[JC1E(,'8ISz4:z/R,XZ/QOs0C"bQ!rD9es(=5b{qm[ M6":`v3-(T;7jMW|@\^_@jZkxF(v0Л& ================================================ FILE: levels/conflict/.githug/objects/75/179304f4fab00613f08a9412b6cb0965bfa564 ================================================ xKn@ @Y)@瓤=\q j'AtI^bO֩~V3=8GRQ"b I&LU4 wY1%QP:3axTN~mYrj mY% ?]L߇#a0ztbqX_۶yV8Db ================================================ FILE: levels/conflict/.githug/objects/88/e0473c9da347c6311f5f8eca8d256bf25402b6 ================================================ x+)JMU06c040031Q(O+(ap1kzY)x׌ / ================================================ FILE: levels/conflict/.githug/objects/bd/c7bec8acae9b3eabf0a15b223a48211b7a89a1 ================================================ xA 0@Q9\@dd"'4[h =y,SGth*d%F٫!eR1l 6D®tET%v%?$OEzynpZ:A汾i\ XbǞͰ?6O(C3QYu1?6lJ ================================================ FILE: levels/conflict/.githug/refs/heads/master ================================================ c797f979cf24ba148bf10d5e26f5d7402dd5f2e1 ================================================ FILE: levels/conflict/.githug/refs/heads/mybranch ================================================ 3d7aec017559be2b61cab850dafdcb2b6212f1c3 ================================================ FILE: levels/conflict/poem.txt ================================================ Humpty dumpty Categorized shoes by color Humpty dumpty Had a great fall ================================================ FILE: levels/conflict.rb ================================================ difficulty 4 description "You need to merge mybranch into the current branch (master). But there may be some incorrect changes in mybranch which may cause conflicts. Solve any merge-conflicts you come across and finish the merge." setup do init_from_level system "git branch -m master" end solution do solved = true solved = false unless repo.head.name == "master" solved = false unless repo.commits("master")[0].parents.length == 2 txt = File.read("poem.txt") solved = false if txt =~ /[<>=|]/ solved = false unless txt =~ /Sat on a wall/ solved end hint do puts ["First you have to do a merge. Then resolve any conflicts and finish the merge", "Take a look at the sections on merge conflicts in 'git merge'.", "Remove the unnecessary lines in `poem.txt`, so only the correct poem remains."] end ================================================ FILE: levels/contribute.rb ================================================ difficulty 3 description "This is the final level, the goal is to contribute to this repository by making a pull request on GitHub. Please note that this level is designed to encourage you to add a valid contribution to Githug, not testing your ability to create a pull request. Contributions that are likely to be accepted are levels, bug fixes and improved documentation." solution do location = "/tmp/githug" FileUtils.rm_rf(location) puts "Cloning repository to #{location}" `git clone https://github.com/Gazler/githug #{location}` contributor = false repo = Grit::Repo.new(location) repo.commits('master', false).each do |commit| if commit.author.name == repo.config["user.name"] if commit.author.email == repo.config["user.email"] contributor = true end end end contributor end hint do puts "Forking the repository would be a good start!" end ================================================ FILE: levels/delete_branch/.githug/COMMIT_EDITMSG ================================================ delete_me branch ================================================ FILE: levels/delete_branch/.githug/HEAD ================================================ ref: refs/heads/master ================================================ FILE: levels/delete_branch/.githug/config ================================================ [core] repositoryformatversion = 0 filemode = true bare = false logallrefupdates = true ignorecase = true precomposeunicode = false ================================================ FILE: levels/delete_branch/.githug/description ================================================ Unnamed repository; edit this file 'description' to name the repository. ================================================ FILE: levels/delete_branch/.githug/hooks/applypatch-msg.sample ================================================ #!/bin/sh # # An example hook script to check the commit log message taken by # applypatch from an e-mail message. # # The hook should exit with non-zero status after issuing an # appropriate message if it wants to stop the commit. The hook is # allowed to edit the commit message file. # # To enable this hook, rename this file to "applypatch-msg". . git-sh-setup test -x "$GIT_DIR/hooks/commit-msg" && exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"} : ================================================ FILE: levels/delete_branch/.githug/hooks/commit-msg.sample ================================================ #!/bin/sh # # An example hook script to check the commit log message. # Called by "git commit" with one argument, the name of the file # that has the commit message. The hook should exit with non-zero # status after issuing an appropriate message if it wants to stop the # commit. The hook is allowed to edit the commit message file. # # To enable this hook, rename this file to "commit-msg". # Uncomment the below to add a Signed-off-by line to the message. # Doing this in a hook is a bad idea in general, but the prepare-commit-msg # hook is more suited to it. # # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" # This example catches duplicate Signed-off-by lines. test "" = "$(grep '^Signed-off-by: ' "$1" | sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { echo >&2 Duplicate Signed-off-by lines. exit 1 } ================================================ FILE: levels/delete_branch/.githug/hooks/post-update.sample ================================================ #!/bin/sh # # An example hook script to prepare a packed repository for use over # dumb transports. # # To enable this hook, rename this file to "post-update". exec git update-server-info ================================================ FILE: levels/delete_branch/.githug/hooks/pre-applypatch.sample ================================================ #!/bin/sh # # An example hook script to verify what is about to be committed # by applypatch from an e-mail message. # # The hook should exit with non-zero status after issuing an # appropriate message if it wants to stop the commit. # # To enable this hook, rename this file to "pre-applypatch". . git-sh-setup test -x "$GIT_DIR/hooks/pre-commit" && exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"} : ================================================ FILE: levels/delete_branch/.githug/hooks/pre-commit.sample ================================================ #!/bin/sh # # An example hook script to verify what is about to be committed. # Called by "git commit" with no arguments. The hook should # exit with non-zero status after issuing an appropriate message if # it wants to stop the commit. # # To enable this hook, rename this file to "pre-commit". if git rev-parse --verify HEAD >/dev/null 2>&1 then against=HEAD else # Initial commit: diff against an empty tree object against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 fi # If you want to allow non-ASCII filenames set this variable to true. allownonascii=$(git config hooks.allownonascii) # Redirect output to stderr. exec 1>&2 # Cross platform projects tend to avoid non-ASCII filenames; prevent # them from being added to the repository. We exploit the fact that the # printable range starts at the space character and ends with tilde. if [ "$allownonascii" != "true" ] && # Note that the use of brackets around a tr range is ok here, (it's # even required, for portability to Solaris 10's /usr/bin/tr), since # the square bracket bytes happen to fall in the designated range. test $(git diff --cached --name-only --diff-filter=A -z $against | LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 then cat <<\EOF Error: Attempt to add a non-ASCII file name. This can cause problems if you want to work with people on other platforms. To be portable it is advisable to rename the file. If you know what you are doing you can disable this check using: git config hooks.allownonascii true EOF exit 1 fi # If there are whitespace errors, print the offending file names and fail. exec git diff-index --check --cached $against -- ================================================ FILE: levels/delete_branch/.githug/hooks/pre-push.sample ================================================ #!/bin/sh # An example hook script to verify what is about to be pushed. Called by "git # push" after it has checked the remote status, but before anything has been # pushed. If this script exits with a non-zero status nothing will be pushed. # # This hook is called with the following parameters: # # $1 -- Name of the remote to which the push is being done # $2 -- URL to which the push is being done # # If pushing without using a named remote those arguments will be equal. # # Information about the commits which are being pushed is supplied as lines to # the standard input in the form: # # # # This sample shows how to prevent push of commits where the log message starts # with "WIP" (work in progress). remote="$1" url="$2" z40=0000000000000000000000000000000000000000 IFS=' ' while read local_ref local_sha remote_ref remote_sha do if [ "$local_sha" = $z40 ] then # Handle delete else if [ "$remote_sha" = $z40 ] then # New branch, examine all commits range="$local_sha" else # Update to existing branch, examine new commits range="$remote_sha..$local_sha" fi # Check for WIP commit commit=`git rev-list -n 1 --grep '^WIP' "$range"` if [ -n "$commit" ] then echo "Found WIP commit in $local_ref, not pushing" exit 1 fi fi done exit 0 ================================================ FILE: levels/delete_branch/.githug/hooks/pre-rebase.sample ================================================ #!/bin/sh # # Copyright (c) 2006, 2008 Junio C Hamano # # The "pre-rebase" hook is run just before "git rebase" starts doing # its job, and can prevent the command from running by exiting with # non-zero status. # # The hook is called with the following parameters: # # $1 -- the upstream the series was forked from. # $2 -- the branch being rebased (or empty when rebasing the current branch). # # This sample shows how to prevent topic branches that are already # merged to 'next' branch from getting rebased, because allowing it # would result in rebasing already published history. publish=next basebranch="$1" if test "$#" = 2 then topic="refs/heads/$2" else topic=`git symbolic-ref HEAD` || exit 0 ;# we do not interrupt rebasing detached HEAD fi case "$topic" in refs/heads/??/*) ;; *) exit 0 ;# we do not interrupt others. ;; esac # Now we are dealing with a topic branch being rebased # on top of master. Is it OK to rebase it? # Does the topic really exist? git show-ref -q "$topic" || { echo >&2 "No such branch $topic" exit 1 } # Is topic fully merged to master? not_in_master=`git rev-list --pretty=oneline ^master "$topic"` if test -z "$not_in_master" then echo >&2 "$topic is fully merged to master; better remove it." exit 1 ;# we could allow it, but there is no point. fi # Is topic ever merged to next? If so you should not be rebasing it. only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` only_next_2=`git rev-list ^master ${publish} | sort` if test "$only_next_1" = "$only_next_2" then not_in_topic=`git rev-list "^$topic" master` if test -z "$not_in_topic" then echo >&2 "$topic is already up-to-date with master" exit 1 ;# we could allow it, but there is no point. else exit 0 fi else not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` /usr/bin/perl -e ' my $topic = $ARGV[0]; my $msg = "* $topic has commits already merged to public branch:\n"; my (%not_in_next) = map { /^([0-9a-f]+) /; ($1 => 1); } split(/\n/, $ARGV[1]); for my $elem (map { /^([0-9a-f]+) (.*)$/; [$1 => $2]; } split(/\n/, $ARGV[2])) { if (!exists $not_in_next{$elem->[0]}) { if ($msg) { print STDERR $msg; undef $msg; } print STDERR " $elem->[1]\n"; } } ' "$topic" "$not_in_next" "$not_in_master" exit 1 fi exit 0 ################################################################ This sample hook safeguards topic branches that have been published from being rewound. The workflow assumed here is: * Once a topic branch forks from "master", "master" is never merged into it again (either directly or indirectly). * Once a topic branch is fully cooked and merged into "master", it is deleted. If you need to build on top of it to correct earlier mistakes, a new topic branch is created by forking at the tip of the "master". This is not strictly necessary, but it makes it easier to keep your history simple. * Whenever you need to test or publish your changes to topic branches, merge them into "next" branch. The script, being an example, hardcodes the publish branch name to be "next", but it is trivial to make it configurable via $GIT_DIR/config mechanism. With this workflow, you would want to know: (1) ... if a topic branch has ever been merged to "next". Young topic branches can have stupid mistakes you would rather clean up before publishing, and things that have not been merged into other branches can be easily rebased without affecting other people. But once it is published, you would not want to rewind it. (2) ... if a topic branch has been fully merged to "master". Then you can delete it. More importantly, you should not build on top of it -- other people may already want to change things related to the topic as patches against your "master", so if you need further changes, it is better to fork the topic (perhaps with the same name) afresh from the tip of "master". Let's look at this example: o---o---o---o---o---o---o---o---o---o "next" / / / / / a---a---b A / / / / / / / / c---c---c---c B / / / / \ / / / / b---b C \ / / / / / \ / ---o---o---o---o---o---o---o---o---o---o---o "master" A, B and C are topic branches. * A has one fix since it was merged up to "next". * B has finished. It has been fully merged up to "master" and "next", and is ready to be deleted. * C has not merged to "next" at all. We would want to allow C to be rebased, refuse A, and encourage B to be deleted. To compute (1): git rev-list ^master ^topic next git rev-list ^master next if these match, topic has not merged in next at all. To compute (2): git rev-list master..topic if this is empty, it is fully merged to "master". ================================================ FILE: levels/delete_branch/.githug/hooks/prepare-commit-msg.sample ================================================ #!/bin/sh # # An example hook script to prepare the commit log message. # Called by "git commit" with the name of the file that has the # commit message, followed by the description of the commit # message's source. The hook's purpose is to edit the commit # message file. If the hook fails with a non-zero status, # the commit is aborted. # # To enable this hook, rename this file to "prepare-commit-msg". # This hook includes three examples. The first comments out the # "Conflicts:" part of a merge commit. # # The second includes the output of "git diff --name-status -r" # into the message, just before the "git status" output. It is # commented because it doesn't cope with --amend or with squashed # commits. # # The third example adds a Signed-off-by line to the message, that can # still be edited. This is rarely a good idea. case "$2,$3" in merge,) /usr/bin/perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;; # ,|template,) # /usr/bin/perl -i.bak -pe ' # print "\n" . `git diff --cached --name-status -r` # if /^#/ && $first++ == 0' "$1" ;; *) ;; esac # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" ================================================ FILE: levels/delete_branch/.githug/hooks/update.sample ================================================ #!/bin/sh # # An example hook script to blocks unannotated tags from entering. # Called by "git receive-pack" with arguments: refname sha1-old sha1-new # # To enable this hook, rename this file to "update". # # Config # ------ # hooks.allowunannotated # This boolean sets whether unannotated tags will be allowed into the # repository. By default they won't be. # hooks.allowdeletetag # This boolean sets whether deleting tags will be allowed in the # repository. By default they won't be. # hooks.allowmodifytag # This boolean sets whether a tag may be modified after creation. By default # it won't be. # hooks.allowdeletebranch # This boolean sets whether deleting branches will be allowed in the # repository. By default they won't be. # hooks.denycreatebranch # This boolean sets whether remotely creating branches will be denied # in the repository. By default this is allowed. # # --- Command line refname="$1" oldrev="$2" newrev="$3" # --- Safety check if [ -z "$GIT_DIR" ]; then echo "Don't run this script from the command line." >&2 echo " (if you want, you could supply GIT_DIR then run" >&2 echo " $0 )" >&2 exit 1 fi if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then echo "usage: $0 " >&2 exit 1 fi # --- Config allowunannotated=$(git config --bool hooks.allowunannotated) allowdeletebranch=$(git config --bool hooks.allowdeletebranch) denycreatebranch=$(git config --bool hooks.denycreatebranch) allowdeletetag=$(git config --bool hooks.allowdeletetag) allowmodifytag=$(git config --bool hooks.allowmodifytag) # check for no description projectdesc=$(sed -e '1q' "$GIT_DIR/description") case "$projectdesc" in "Unnamed repository"* | "") echo "*** Project description file hasn't been set" >&2 exit 1 ;; esac # --- Check types # if $newrev is 0000...0000, it's a commit to delete a ref. zero="0000000000000000000000000000000000000000" if [ "$newrev" = "$zero" ]; then newrev_type=delete else newrev_type=$(git cat-file -t $newrev) fi case "$refname","$newrev_type" in refs/tags/*,commit) # un-annotated tag short_refname=${refname##refs/tags/} if [ "$allowunannotated" != "true" ]; then echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2 echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 exit 1 fi ;; refs/tags/*,delete) # delete tag if [ "$allowdeletetag" != "true" ]; then echo "*** Deleting a tag is not allowed in this repository" >&2 exit 1 fi ;; refs/tags/*,tag) # annotated tag if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 then echo "*** Tag '$refname' already exists." >&2 echo "*** Modifying a tag is not allowed in this repository." >&2 exit 1 fi ;; refs/heads/*,commit) # branch if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then echo "*** Creating a branch is not allowed in this repository" >&2 exit 1 fi ;; refs/heads/*,delete) # delete branch if [ "$allowdeletebranch" != "true" ]; then echo "*** Deleting a branch is not allowed in this repository" >&2 exit 1 fi ;; refs/remotes/*,commit) # tracking branch ;; refs/remotes/*,delete) # delete tracking branch if [ "$allowdeletebranch" != "true" ]; then echo "*** Deleting a tracking branch is not allowed in this repository" >&2 exit 1 fi ;; *) # Anything else (is there anything else?) echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 exit 1 ;; esac # --- Finished exit 0 ================================================ FILE: levels/delete_branch/.githug/info/exclude ================================================ # git ls-files --others --exclude-from=.git/info/exclude # Lines that start with '#' are comments. # For a project mostly in C, the following would be a good set of # exclude patterns (uncomment them if you want to use them): # *.[oa] # *~ ================================================ FILE: levels/delete_branch/.githug/logs/HEAD ================================================ 0000000000000000000000000000000000000000 b60afe294eb3c200d646995c9e0234470157c1b0 Daniel Smilansky 1386225561 -0800 commit (initial): first commit b60afe294eb3c200d646995c9e0234470157c1b0 b60afe294eb3c200d646995c9e0234470157c1b0 Daniel Smilansky 1386225573 -0800 checkout: moving from master to master b60afe294eb3c200d646995c9e0234470157c1b0 b60afe294eb3c200d646995c9e0234470157c1b0 Daniel Smilansky 1386225580 -0800 checkout: moving from master to delete_me b60afe294eb3c200d646995c9e0234470157c1b0 b60afe294eb3c200d646995c9e0234470157c1b0 Daniel Smilansky 1386225584 -0800 checkout: moving from delete_me to master ================================================ FILE: levels/delete_branch/.githug/logs/refs/heads/delete_me ================================================ 0000000000000000000000000000000000000000 b60afe294eb3c200d646995c9e0234470157c1b0 Daniel Smilansky 1386225568 -0800 branch: Created from master ================================================ FILE: levels/delete_branch/.githug/logs/refs/heads/master ================================================ 0000000000000000000000000000000000000000 b60afe294eb3c200d646995c9e0234470157c1b0 Daniel Smilansky 1386225561 -0800 commit (initial): first commit ================================================ FILE: levels/delete_branch/.githug/objects/b6/0afe294eb3c200d646995c9e0234470157c1b0 ================================================ x !aT1 h'v`Kx{+=<_q73'OBc#œwFh6zzLk 2%> |ǘgNJx|%1?Q ================================================ FILE: levels/delete_branch/.githug/refs/heads/delete_me ================================================ b60afe294eb3c200d646995c9e0234470157c1b0 ================================================ FILE: levels/delete_branch/.githug/refs/heads/master ================================================ b60afe294eb3c200d646995c9e0234470157c1b0 ================================================ FILE: levels/delete_branch/readme ================================================ ================================================ FILE: levels/delete_branch.rb ================================================ difficulty 2 description "You have created too many branches for your project. There is an old branch in your repo called 'delete_me', you should delete it." setup do init_from_level system "git branch -m master" end solution do return true unless repo.branches.map(&:name).include?('delete_me') end hint do puts "Running 'git --help branch' will give you a list of branch commands." end ================================================ FILE: levels/diff/.githug/COMMIT_EDITMSG ================================================ added app.rb ================================================ FILE: levels/diff/.githug/HEAD ================================================ ref: refs/heads/master ================================================ FILE: levels/diff/.githug/config ================================================ [core] repositoryformatversion = 0 filemode = true bare = false logallrefupdates = true ================================================ FILE: levels/diff/.githug/description ================================================ Unnamed repository; edit this file 'description' to name the repository. ================================================ FILE: levels/diff/.githug/hooks/applypatch-msg.sample ================================================ #!/bin/sh # # An example hook script to check the commit log message taken by # applypatch from an e-mail message. # # The hook should exit with non-zero status after issuing an # appropriate message if it wants to stop the commit. The hook is # allowed to edit the commit message file. # # To enable this hook, rename this file to "applypatch-msg". . git-sh-setup test -x "$GIT_DIR/hooks/commit-msg" && exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"} : ================================================ FILE: levels/diff/.githug/hooks/commit-msg.sample ================================================ #!/bin/sh # # An example hook script to check the commit log message. # Called by "git commit" with one argument, the name of the file # that has the commit message. The hook should exit with non-zero # status after issuing an appropriate message if it wants to stop the # commit. The hook is allowed to edit the commit message file. # # To enable this hook, rename this file to "commit-msg". # Uncomment the below to add a Signed-off-by line to the message. # Doing this in a hook is a bad idea in general, but the prepare-commit-msg # hook is more suited to it. # # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" # This example catches duplicate Signed-off-by lines. test "" = "$(grep '^Signed-off-by: ' "$1" | sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { echo >&2 Duplicate Signed-off-by lines. exit 1 } ================================================ FILE: levels/diff/.githug/hooks/post-commit.sample ================================================ #!/bin/sh # # An example hook script that is called after a successful # commit is made. # # To enable this hook, rename this file to "post-commit". : Nothing ================================================ FILE: levels/diff/.githug/hooks/post-receive.sample ================================================ #!/bin/sh # # An example hook script for the "post-receive" event. # # The "post-receive" script is run after receive-pack has accepted a pack # and the repository has been updated. It is passed arguments in through # stdin in the form # # For example: # aa453216d1b3e49e7f6f98441fa56946ddcd6a20 68f7abf4e6f922807889f52bc043ecd31b79f814 refs/heads/master # # see contrib/hooks/ for a sample, or uncomment the next line and # rename the file to "post-receive". #. /usr/share/doc/git-core/contrib/hooks/post-receive-email ================================================ FILE: levels/diff/.githug/hooks/post-update.sample ================================================ #!/bin/sh # # An example hook script to prepare a packed repository for use over # dumb transports. # # To enable this hook, rename this file to "post-update". exec git update-server-info ================================================ FILE: levels/diff/.githug/hooks/pre-applypatch.sample ================================================ #!/bin/sh # # An example hook script to verify what is about to be committed # by applypatch from an e-mail message. # # The hook should exit with non-zero status after issuing an # appropriate message if it wants to stop the commit. # # To enable this hook, rename this file to "pre-applypatch". . git-sh-setup test -x "$GIT_DIR/hooks/pre-commit" && exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"} : ================================================ FILE: levels/diff/.githug/hooks/pre-commit.sample ================================================ #!/bin/sh # # An example hook script to verify what is about to be committed. # Called by "git commit" with no arguments. The hook should # exit with non-zero status after issuing an appropriate message if # it wants to stop the commit. # # To enable this hook, rename this file to "pre-commit". if git rev-parse --verify HEAD >/dev/null 2>&1 then against=HEAD else # Initial commit: diff against an empty tree object against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 fi # If you want to allow non-ascii filenames set this variable to true. allownonascii=$(git config hooks.allownonascii) # Cross platform projects tend to avoid non-ascii filenames; prevent # them from being added to the repository. We exploit the fact that the # printable range starts at the space character and ends with tilde. if [ "$allownonascii" != "true" ] && # Note that the use of brackets around a tr range is ok here, (it's # even required, for portability to Solaris 10's /usr/bin/tr), since # the square bracket bytes happen to fall in the designated range. test "$(git diff --cached --name-only --diff-filter=A -z $against | LC_ALL=C tr -d '[ -~]\0')" then echo "Error: Attempt to add a non-ascii file name." echo echo "This can cause problems if you want to work" echo "with people on other platforms." echo echo "To be portable it is advisable to rename the file ..." echo echo "If you know what you are doing you can disable this" echo "check using:" echo echo " git config hooks.allownonascii true" echo exit 1 fi exec git diff-index --check --cached $against -- ================================================ FILE: levels/diff/.githug/hooks/pre-rebase.sample ================================================ #!/bin/sh # # Copyright (c) 2006, 2008 Junio C Hamano # # The "pre-rebase" hook is run just before "git rebase" starts doing # its job, and can prevent the command from running by exiting with # non-zero status. # # The hook is called with the following parameters: # # $1 -- the upstream the series was forked from. # $2 -- the branch being rebased (or empty when rebasing the current branch). # # This sample shows how to prevent topic branches that are already # merged to 'next' branch from getting rebased, because allowing it # would result in rebasing already published history. publish=next basebranch="$1" if test "$#" = 2 then topic="refs/heads/$2" else topic=`git symbolic-ref HEAD` || exit 0 ;# we do not interrupt rebasing detached HEAD fi case "$topic" in refs/heads/??/*) ;; *) exit 0 ;# we do not interrupt others. ;; esac # Now we are dealing with a topic branch being rebased # on top of master. Is it OK to rebase it? # Does the topic really exist? git show-ref -q "$topic" || { echo >&2 "No such branch $topic" exit 1 } # Is topic fully merged to master? not_in_master=`git rev-list --pretty=oneline ^master "$topic"` if test -z "$not_in_master" then echo >&2 "$topic is fully merged to master; better remove it." exit 1 ;# we could allow it, but there is no point. fi # Is topic ever merged to next? If so you should not be rebasing it. only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` only_next_2=`git rev-list ^master ${publish} | sort` if test "$only_next_1" = "$only_next_2" then not_in_topic=`git rev-list "^$topic" master` if test -z "$not_in_topic" then echo >&2 "$topic is already up-to-date with master" exit 1 ;# we could allow it, but there is no point. else exit 0 fi else not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` /usr/bin/perl -e ' my $topic = $ARGV[0]; my $msg = "* $topic has commits already merged to public branch:\n"; my (%not_in_next) = map { /^([0-9a-f]+) /; ($1 => 1); } split(/\n/, $ARGV[1]); for my $elem (map { /^([0-9a-f]+) (.*)$/; [$1 => $2]; } split(/\n/, $ARGV[2])) { if (!exists $not_in_next{$elem->[0]}) { if ($msg) { print STDERR $msg; undef $msg; } print STDERR " $elem->[1]\n"; } } ' "$topic" "$not_in_next" "$not_in_master" exit 1 fi exit 0 ################################################################ This sample hook safeguards topic branches that have been published from being rewound. The workflow assumed here is: * Once a topic branch forks from "master", "master" is never merged into it again (either directly or indirectly). * Once a topic branch is fully cooked and merged into "master", it is deleted. If you need to build on top of it to correct earlier mistakes, a new topic branch is created by forking at the tip of the "master". This is not strictly necessary, but it makes it easier to keep your history simple. * Whenever you need to test or publish your changes to topic branches, merge them into "next" branch. The script, being an example, hardcodes the publish branch name to be "next", but it is trivial to make it configurable via $GIT_DIR/config mechanism. With this workflow, you would want to know: (1) ... if a topic branch has ever been merged to "next". Young topic branches can have stupid mistakes you would rather clean up before publishing, and things that have not been merged into other branches can be easily rebased without affecting other people. But once it is published, you would not want to rewind it. (2) ... if a topic branch has been fully merged to "master". Then you can delete it. More importantly, you should not build on top of it -- other people may already want to change things related to the topic as patches against your "master", so if you need further changes, it is better to fork the topic (perhaps with the same name) afresh from the tip of "master". Let's look at this example: o---o---o---o---o---o---o---o---o---o "next" / / / / / a---a---b A / / / / / / / / c---c---c---c B / / / / \ / / / / b---b C \ / / / / / \ / ---o---o---o---o---o---o---o---o---o---o---o "master" A, B and C are topic branches. * A has one fix since it was merged up to "next". * B has finished. It has been fully merged up to "master" and "next", and is ready to be deleted. * C has not merged to "next" at all. We would want to allow C to be rebased, refuse A, and encourage B to be deleted. To compute (1): git rev-list ^master ^topic next git rev-list ^master next if these match, topic has not merged in next at all. To compute (2): git rev-list master..topic if this is empty, it is fully merged to "master". ================================================ FILE: levels/diff/.githug/hooks/prepare-commit-msg.sample ================================================ #!/bin/sh # # An example hook script to prepare the commit log message. # Called by "git commit" with the name of the file that has the # commit message, followed by the description of the commit # message's source. The hook's purpose is to edit the commit # message file. If the hook fails with a non-zero status, # the commit is aborted. # # To enable this hook, rename this file to "prepare-commit-msg". # This hook includes three examples. The first comments out the # "Conflicts:" part of a merge commit. # # The second includes the output of "git diff --name-status -r" # into the message, just before the "git status" output. It is # commented because it doesn't cope with --amend or with squashed # commits. # # The third example adds a Signed-off-by line to the message, that can # still be edited. This is rarely a good idea. case "$2,$3" in merge,) /usr/bin/perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;; # ,|template,) # /usr/bin/perl -i.bak -pe ' # print "\n" . `git diff --cached --name-status -r` # if /^#/ && $first++ == 0' "$1" ;; *) ;; esac # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" ================================================ FILE: levels/diff/.githug/hooks/update.sample ================================================ #!/bin/sh # # An example hook script to blocks unannotated tags from entering. # Called by "git receive-pack" with arguments: refname sha1-old sha1-new # # To enable this hook, rename this file to "update". # # Config # ------ # hooks.allowunannotated # This boolean sets whether unannotated tags will be allowed into the # repository. By default they won't be. # hooks.allowdeletetag # This boolean sets whether deleting tags will be allowed in the # repository. By default they won't be. # hooks.allowmodifytag # This boolean sets whether a tag may be modified after creation. By default # it won't be. # hooks.allowdeletebranch # This boolean sets whether deleting branches will be allowed in the # repository. By default they won't be. # hooks.denycreatebranch # This boolean sets whether remotely creating branches will be denied # in the repository. By default this is allowed. # # --- Command line refname="$1" oldrev="$2" newrev="$3" # --- Safety check if [ -z "$GIT_DIR" ]; then echo "Don't run this script from the command line." >&2 echo " (if you want, you could supply GIT_DIR then run" >&2 echo " $0 )" >&2 exit 1 fi if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then echo "Usage: $0 " >&2 exit 1 fi # --- Config allowunannotated=$(git config --bool hooks.allowunannotated) allowdeletebranch=$(git config --bool hooks.allowdeletebranch) denycreatebranch=$(git config --bool hooks.denycreatebranch) allowdeletetag=$(git config --bool hooks.allowdeletetag) allowmodifytag=$(git config --bool hooks.allowmodifytag) # check for no description projectdesc=$(sed -e '1q' "$GIT_DIR/description") case "$projectdesc" in "Unnamed repository"* | "") echo "*** Project description file hasn't been set" >&2 exit 1 ;; esac # --- Check types # if $newrev is 0000...0000, it's a commit to delete a ref. zero="0000000000000000000000000000000000000000" if [ "$newrev" = "$zero" ]; then newrev_type=delete else newrev_type=$(git cat-file -t $newrev) fi case "$refname","$newrev_type" in refs/tags/*,commit) # un-annotated tag short_refname=${refname##refs/tags/} if [ "$allowunannotated" != "true" ]; then echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2 echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 exit 1 fi ;; refs/tags/*,delete) # delete tag if [ "$allowdeletetag" != "true" ]; then echo "*** Deleting a tag is not allowed in this repository" >&2 exit 1 fi ;; refs/tags/*,tag) # annotated tag if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 then echo "*** Tag '$refname' already exists." >&2 echo "*** Modifying a tag is not allowed in this repository." >&2 exit 1 fi ;; refs/heads/*,commit) # branch if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then echo "*** Creating a branch is not allowed in this repository" >&2 exit 1 fi ;; refs/heads/*,delete) # delete branch if [ "$allowdeletebranch" != "true" ]; then echo "*** Deleting a branch is not allowed in this repository" >&2 exit 1 fi ;; refs/remotes/*,commit) # tracking branch ;; refs/remotes/*,delete) # delete tracking branch if [ "$allowdeletebranch" != "true" ]; then echo "*** Deleting a tracking branch is not allowed in this repository" >&2 exit 1 fi ;; *) # Anything else (is there anything else?) echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 exit 1 ;; esac # --- Finished exit 0 ================================================ FILE: levels/diff/.githug/info/exclude ================================================ # git ls-files --others --exclude-from=.git/info/exclude # Lines that start with '#' are comments. # For a project mostly in C, the following would be a good set of # exclude patterns (uncomment them if you want to use them): # *.[oa] # *~ ================================================ FILE: levels/diff/.githug/logs/HEAD ================================================ 0000000000000000000000000000000000000000 dcaa55e97af34402e84d5336da37abcccc23cba6 Gary Rennie 1331585213 +0000 commit (initial): added app.rb ================================================ FILE: levels/diff/.githug/logs/refs/heads/master ================================================ 0000000000000000000000000000000000000000 dcaa55e97af34402e84d5336da37abcccc23cba6 Gary Rennie 1331585213 +0000 commit (initial): added app.rb ================================================ FILE: levels/diff/.githug/objects/4f/703ca9bd25781b6758eeb3c42ed5348610ba6d ================================================ xSn03_ajZiQtKfjuZU!o6tڿeos=Dǯ$<\qϩԱ~W-u2 r@Jq+bpGC-B3M͡s|.UUMDS)QUe+~;M'/~]YǫjujʎxCo5;b'Z"i"}߷Y=hb{fO6auJ`8w;容c?CXtKnB}[݆G~ z{U64c` #tm9x5*Dzaƒ  F5u hXa*koT!i֠?fGMh@bX-/]Ntty+y O{F]\ZkE6W:DbXN4^ej\GY㛜]EP~W ================================================ FILE: levels/diff/.githug/objects/dc/aa55e97af34402e84d5336da37abcccc23cba6 ================================================ xM 0F]J&?M EܹdӔ=9*3`Wz4B.ZS$8$f^^*\Bmesx6[>T Pk*p}ٵjRa߇+: ================================================ FILE: levels/diff/.githug/refs/heads/master ================================================ dcaa55e97af34402e84d5336da37abcccc23cba6 ================================================ FILE: levels/diff/app.rb ================================================ require 'sinatra' require 'oauth2' require 'json' enable :sessions def client OAuth2::Client.new("mTeZFqkCmzc8JnjKXaSww95bFFxhUpp1wwmSi8vG", "a9OMyEdW7JvWThHmmvFcShR9P2dyad3EGuA2ULDh", :site => "http://localhost:3000") end get "/auth/test" do redirect client.auth_code.authorize_url(:redirect_uri => redirect_uri) end get '/auth/test/callback' do access_token = client.auth_code.get_token(params[:code], :redirect_uri => redirect_uri) session[:access_token] = access_token.token @message = "Successfully authenticated with the server" erb :success end get '/yet_another' do @message = get_response('data.json') erb :success end get '/another_page' do @message = get_response('server.json') erb :another end def get_response(url) access_token = OAuth2::AccessToken.new(client, session[:access_token]) JSON.parse(access_token.get("/api/v1/#{url}").body) end def redirect_uri uri = URI.parse(request.url) uri.path = '/auth/test/callback' uri.query = nil uri.to_s end ================================================ FILE: levels/diff.rb ================================================ difficulty 2 description "Since your last commit, file `app.rb` was modified. Find out which line has changed." setup do init_from_level system "git branch -m master" end solution do line = request "What is the number of the line which has changed?" return false unless line == "26" true end hint do puts "You are looking for the difference since your last commit. Don't forget that running `git` on its own will list the possible commands." end ================================================ FILE: levels/fetch.rb ================================================ difficulty 2 description "Looks like a new branch was pushed into our remote repository. Get the changes without merging them with the local repository " setup do # remember the working directory so we can come back to it later cwd = Dir.pwd # initialize another git repo to be used as a "remote" tmpdir = Dir.mktmpdir # local repo repo.init system "git branch -m master" # adds a file to origin/master FileUtils.touch "master_file" repo.add "master_file" repo.commit_all 'Commits master_file' # remote repo Dir.chdir tmpdir repo.init # adds a file to origin/master FileUtils.touch "master_file" repo.add "master_file" repo.commit_all 'Commits master_file' # adds remote repo Dir.chdir cwd `git remote add origin #{tmpdir}/.git` `git fetch origin --quiet` `git branch -u origin/master master 2> /dev/null` Dir.chdir tmpdir # create a new branch in the remote repo `git checkout -b new_branch --quiet` # adds a file into the new branch. Should not be pulled into the local FileUtils.touch "file1" repo.add "file1" repo.commit_all 'Commits file 1' end solution do repo.init result = true # counts the number of local branches. Should equal 1 local_branches = repo.branches.size # after a git fetch command, each branch will be stored in in the .git/FETCH_HEAD file. Each branch is on its own line # This command will count the number of lines, which will give the number of branches if File.file?('.git/FETCH_HEAD') # checks for file existence num_remote = File.read(".git/FETCH_HEAD").split("\n").count else num_remote = 0 end # there should be 1 local branch and 2 remote branches for a success condition if local_branches == 1 and num_remote == 2 result = true else result = false end end hint do puts "Look up the 'git fetch' command" end ================================================ FILE: levels/find_old_branch/.githug/COMMIT_EDITMSG ================================================ commit another todo ================================================ FILE: levels/find_old_branch/.githug/HEAD ================================================ ref: refs/heads/kill_the_batman ================================================ FILE: levels/find_old_branch/.githug/ORIG_HEAD ================================================ 05e9c01bd3c9264761dd0cde477400a2c3104642 ================================================ FILE: levels/find_old_branch/.githug/config ================================================ [core] repositoryformatversion = 0 filemode = true bare = false logallrefupdates = true ignorecase = true ================================================ FILE: levels/find_old_branch/.githug/description ================================================ Unnamed repository; edit this file 'description' to name the repository. ================================================ FILE: levels/find_old_branch/.githug/hooks/applypatch-msg.sample ================================================ #!/bin/sh # # An example hook script to check the commit log message taken by # applypatch from an e-mail message. # # The hook should exit with non-zero status after issuing an # appropriate message if it wants to stop the commit. The hook is # allowed to edit the commit message file. # # To enable this hook, rename this file to "applypatch-msg". . git-sh-setup test -x "$GIT_DIR/hooks/commit-msg" && exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"} : ================================================ FILE: levels/find_old_branch/.githug/hooks/commit-msg.sample ================================================ #!/bin/sh # # An example hook script to check the commit log message. # Called by "git commit" with one argument, the name of the file # that has the commit message. The hook should exit with non-zero # status after issuing an appropriate message if it wants to stop the # commit. The hook is allowed to edit the commit message file. # # To enable this hook, rename this file to "commit-msg". # Uncomment the below to add a Signed-off-by line to the message. # Doing this in a hook is a bad idea in general, but the prepare-commit-msg # hook is more suited to it. # # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" # This example catches duplicate Signed-off-by lines. test "" = "$(grep '^Signed-off-by: ' "$1" | sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { echo >&2 Duplicate Signed-off-by lines. exit 1 } ================================================ FILE: levels/find_old_branch/.githug/hooks/post-commit.sample ================================================ #!/bin/sh # # An example hook script that is called after a successful # commit is made. # # To enable this hook, rename this file to "post-commit". : Nothing ================================================ FILE: levels/find_old_branch/.githug/hooks/post-receive.sample ================================================ #!/bin/sh # # An example hook script for the "post-receive" event. # # The "post-receive" script is run after receive-pack has accepted a pack # and the repository has been updated. It is passed arguments in through # stdin in the form # # For example: # aa453216d1b3e49e7f6f98441fa56946ddcd6a20 68f7abf4e6f922807889f52bc043ecd31b79f814 refs/heads/master # # see contrib/hooks/ for a sample, or uncomment the next line and # rename the file to "post-receive". #. /usr/share/doc/git-core/contrib/hooks/post-receive-email ================================================ FILE: levels/find_old_branch/.githug/hooks/post-update.sample ================================================ #!/bin/sh # # An example hook script to prepare a packed repository for use over # dumb transports. # # To enable this hook, rename this file to "post-update". exec git update-server-info ================================================ FILE: levels/find_old_branch/.githug/hooks/pre-applypatch.sample ================================================ #!/bin/sh # # An example hook script to verify what is about to be committed # by applypatch from an e-mail message. # # The hook should exit with non-zero status after issuing an # appropriate message if it wants to stop the commit. # # To enable this hook, rename this file to "pre-applypatch". . git-sh-setup test -x "$GIT_DIR/hooks/pre-commit" && exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"} : ================================================ FILE: levels/find_old_branch/.githug/hooks/pre-commit.sample ================================================ #!/bin/sh # # An example hook script to verify what is about to be committed. # Called by "git commit" with no arguments. The hook should # exit with non-zero status after issuing an appropriate message if # it wants to stop the commit. # # To enable this hook, rename this file to "pre-commit". if git rev-parse --verify HEAD >/dev/null 2>&1 then against=HEAD else # Initial commit: diff against an empty tree object against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 fi # If you want to allow non-ascii filenames set this variable to true. allownonascii=$(git config hooks.allownonascii) # Redirect output to stderr. exec 1>&2 # Cross platform projects tend to avoid non-ascii filenames; prevent # them from being added to the repository. We exploit the fact that the # printable range starts at the space character and ends with tilde. if [ "$allownonascii" != "true" ] && # Note that the use of brackets around a tr range is ok here, (it's # even required, for portability to Solaris 10's /usr/bin/tr), since # the square bracket bytes happen to fall in the designated range. test $(git diff --cached --name-only --diff-filter=A -z $against | LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 then echo "Error: Attempt to add a non-ascii file name." echo echo "This can cause problems if you want to work" echo "with people on other platforms." echo echo "To be portable it is advisable to rename the file ..." echo echo "If you know what you are doing you can disable this" echo "check using:" echo echo " git config hooks.allownonascii true" echo exit 1 fi # If there are whitespace errors, print the offending file names and fail. exec git diff-index --check --cached $against -- ================================================ FILE: levels/find_old_branch/.githug/hooks/pre-rebase.sample ================================================ #!/bin/sh # # Copyright (c) 2006, 2008 Junio C Hamano # # The "pre-rebase" hook is run just before "git rebase" starts doing # its job, and can prevent the command from running by exiting with # non-zero status. # # The hook is called with the following parameters: # # $1 -- the upstream the series was forked from. # $2 -- the branch being rebased (or empty when rebasing the current branch). # # This sample shows how to prevent topic branches that are already # merged to 'next' branch from getting rebased, because allowing it # would result in rebasing already published history. publish=next basebranch="$1" if test "$#" = 2 then topic="refs/heads/$2" else topic=`git symbolic-ref HEAD` || exit 0 ;# we do not interrupt rebasing detached HEAD fi case "$topic" in refs/heads/??/*) ;; *) exit 0 ;# we do not interrupt others. ;; esac # Now we are dealing with a topic branch being rebased # on top of master. Is it OK to rebase it? # Does the topic really exist? git show-ref -q "$topic" || { echo >&2 "No such branch $topic" exit 1 } # Is topic fully merged to master? not_in_master=`git rev-list --pretty=oneline ^master "$topic"` if test -z "$not_in_master" then echo >&2 "$topic is fully merged to master; better remove it." exit 1 ;# we could allow it, but there is no point. fi # Is topic ever merged to next? If so you should not be rebasing it. only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` only_next_2=`git rev-list ^master ${publish} | sort` if test "$only_next_1" = "$only_next_2" then not_in_topic=`git rev-list "^$topic" master` if test -z "$not_in_topic" then echo >&2 "$topic is already up-to-date with master" exit 1 ;# we could allow it, but there is no point. else exit 0 fi else not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` /usr/bin/perl -e ' my $topic = $ARGV[0]; my $msg = "* $topic has commits already merged to public branch:\n"; my (%not_in_next) = map { /^([0-9a-f]+) /; ($1 => 1); } split(/\n/, $ARGV[1]); for my $elem (map { /^([0-9a-f]+) (.*)$/; [$1 => $2]; } split(/\n/, $ARGV[2])) { if (!exists $not_in_next{$elem->[0]}) { if ($msg) { print STDERR $msg; undef $msg; } print STDERR " $elem->[1]\n"; } } ' "$topic" "$not_in_next" "$not_in_master" exit 1 fi exit 0 ################################################################ This sample hook safeguards topic branches that have been published from being rewound. The workflow assumed here is: * Once a topic branch forks from "master", "master" is never merged into it again (either directly or indirectly). * Once a topic branch is fully cooked and merged into "master", it is deleted. If you need to build on top of it to correct earlier mistakes, a new topic branch is created by forking at the tip of the "master". This is not strictly necessary, but it makes it easier to keep your history simple. * Whenever you need to test or publish your changes to topic branches, merge them into "next" branch. The script, being an example, hardcodes the publish branch name to be "next", but it is trivial to make it configurable via $GIT_DIR/config mechanism. With this workflow, you would want to know: (1) ... if a topic branch has ever been merged to "next". Young topic branches can have stupid mistakes you would rather clean up before publishing, and things that have not been merged into other branches can be easily rebased without affecting other people. But once it is published, you would not want to rewind it. (2) ... if a topic branch has been fully merged to "master". Then you can delete it. More importantly, you should not build on top of it -- other people may already want to change things related to the topic as patches against your "master", so if you need further changes, it is better to fork the topic (perhaps with the same name) afresh from the tip of "master". Let's look at this example: o---o---o---o---o---o---o---o---o---o "next" / / / / / a---a---b A / / / / / / / / c---c---c---c B / / / / \ / / / / b---b C \ / / / / / \ / ---o---o---o---o---o---o---o---o---o---o---o "master" A, B and C are topic branches. * A has one fix since it was merged up to "next". * B has finished. It has been fully merged up to "master" and "next", and is ready to be deleted. * C has not merged to "next" at all. We would want to allow C to be rebased, refuse A, and encourage B to be deleted. To compute (1): git rev-list ^master ^topic next git rev-list ^master next if these match, topic has not merged in next at all. To compute (2): git rev-list master..topic if this is empty, it is fully merged to "master". ================================================ FILE: levels/find_old_branch/.githug/hooks/prepare-commit-msg.sample ================================================ #!/bin/sh # # An example hook script to prepare the commit log message. # Called by "git commit" with the name of the file that has the # commit message, followed by the description of the commit # message's source. The hook's purpose is to edit the commit # message file. If the hook fails with a non-zero status, # the commit is aborted. # # To enable this hook, rename this file to "prepare-commit-msg". # This hook includes three examples. The first comments out the # "Conflicts:" part of a merge commit. # # The second includes the output of "git diff --name-status -r" # into the message, just before the "git status" output. It is # commented because it doesn't cope with --amend or with squashed # commits. # # The third example adds a Signed-off-by line to the message, that can # still be edited. This is rarely a good idea. case "$2,$3" in merge,) /usr/bin/perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;; # ,|template,) # /usr/bin/perl -i.bak -pe ' # print "\n" . `git diff --cached --name-status -r` # if /^#/ && $first++ == 0' "$1" ;; *) ;; esac # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" ================================================ FILE: levels/find_old_branch/.githug/hooks/update.sample ================================================ #!/bin/sh # # An example hook script to blocks unannotated tags from entering. # Called by "git receive-pack" with arguments: refname sha1-old sha1-new # # To enable this hook, rename this file to "update". # # Config # ------ # hooks.allowunannotated # This boolean sets whether unannotated tags will be allowed into the # repository. By default they won't be. # hooks.allowdeletetag # This boolean sets whether deleting tags will be allowed in the # repository. By default they won't be. # hooks.allowmodifytag # This boolean sets whether a tag may be modified after creation. By default # it won't be. # hooks.allowdeletebranch # This boolean sets whether deleting branches will be allowed in the # repository. By default they won't be. # hooks.denycreatebranch # This boolean sets whether remotely creating branches will be denied # in the repository. By default this is allowed. # # --- Command line refname="$1" oldrev="$2" newrev="$3" # --- Safety check if [ -z "$GIT_DIR" ]; then echo "Don't run this script from the command line." >&2 echo " (if you want, you could supply GIT_DIR then run" >&2 echo " $0 )" >&2 exit 1 fi if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then echo "Usage: $0 " >&2 exit 1 fi # --- Config allowunannotated=$(git config --bool hooks.allowunannotated) allowdeletebranch=$(git config --bool hooks.allowdeletebranch) denycreatebranch=$(git config --bool hooks.denycreatebranch) allowdeletetag=$(git config --bool hooks.allowdeletetag) allowmodifytag=$(git config --bool hooks.allowmodifytag) # check for no description projectdesc=$(sed -e '1q' "$GIT_DIR/description") case "$projectdesc" in "Unnamed repository"* | "") echo "*** Project description file hasn't been set" >&2 exit 1 ;; esac # --- Check types # if $newrev is 0000...0000, it's a commit to delete a ref. zero="0000000000000000000000000000000000000000" if [ "$newrev" = "$zero" ]; then newrev_type=delete else newrev_type=$(git cat-file -t $newrev) fi case "$refname","$newrev_type" in refs/tags/*,commit) # un-annotated tag short_refname=${refname##refs/tags/} if [ "$allowunannotated" != "true" ]; then echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2 echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 exit 1 fi ;; refs/tags/*,delete) # delete tag if [ "$allowdeletetag" != "true" ]; then echo "*** Deleting a tag is not allowed in this repository" >&2 exit 1 fi ;; refs/tags/*,tag) # annotated tag if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 then echo "*** Tag '$refname' already exists." >&2 echo "*** Modifying a tag is not allowed in this repository." >&2 exit 1 fi ;; refs/heads/*,commit) # branch if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then echo "*** Creating a branch is not allowed in this repository" >&2 exit 1 fi ;; refs/heads/*,delete) # delete branch if [ "$allowdeletebranch" != "true" ]; then echo "*** Deleting a branch is not allowed in this repository" >&2 exit 1 fi ;; refs/remotes/*,commit) # tracking branch ;; refs/remotes/*,delete) # delete tracking branch if [ "$allowdeletebranch" != "true" ]; then echo "*** Deleting a tracking branch is not allowed in this repository" >&2 exit 1 fi ;; *) # Anything else (is there anything else?) echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 exit 1 ;; esac # --- Finished exit 0 ================================================ FILE: levels/find_old_branch/.githug/info/exclude ================================================ # git ls-files --others --exclude-from=.git/info/exclude # Lines that start with '#' are comments. # For a project mostly in C, the following would be a good set of # exclude patterns (uncomment them if you want to use them): # *.[oa] # *~ .DS_Store ================================================ FILE: levels/find_old_branch/.githug/logs/HEAD ================================================ 0000000000000000000000000000000000000000 6876e5b41fb693190df76b1baef6ef98623b4f1a mcramm 1332283192 -0600 commit (initial): initial commit 6876e5b41fb693190df76b1baef6ef98623b4f1a 6876e5b41fb693190df76b1baef6ef98623b4f1a mcramm 1332283224 -0600 checkout: moving from cure_common_cold to kill_the_batman 6876e5b41fb693190df76b1baef6ef98623b4f1a 6876e5b41fb693190df76b1baef6ef98623b4f1a mcramm 1332283240 -0600 checkout: moving from kill_the_batman to blowup_sun_for_ransom 6876e5b41fb693190df76b1baef6ef98623b4f1a 6876e5b41fb693190df76b1baef6ef98623b4f1a mcramm 1332283272 -0600 checkout: moving from blowup_sun_for_ransom to solve_world_hunger 6876e5b41fb693190df76b1baef6ef98623b4f1a 324336a8401afc8ca384eaafe6615c84d552dd2c mcramm 1332283302 -0600 commit: commit todo 324336a8401afc8ca384eaafe6615c84d552dd2c 6876e5b41fb693190df76b1baef6ef98623b4f1a mcramm 1332283316 -0600 checkout: moving from solve_world_hunger to kill_the_batman 6876e5b41fb693190df76b1baef6ef98623b4f1a 894a16d6f1a48224e9006b4a6f0fe3846da19bec mcramm 1332283505 -0600 commit: commit another todo ================================================ FILE: levels/find_old_branch/.githug/logs/refs/heads/blowup_sun_for_ransom ================================================ 0000000000000000000000000000000000000000 6876e5b41fb693190df76b1baef6ef98623b4f1a mcramm 1332283240 -0600 branch: Created from HEAD ================================================ FILE: levels/find_old_branch/.githug/logs/refs/heads/cure_common_cold ================================================ 0000000000000000000000000000000000000000 6876e5b41fb693190df76b1baef6ef98623b4f1a mcramm 1332283192 -0600 commit (initial): initial commit 6876e5b41fb693190df76b1baef6ef98623b4f1a 6876e5b41fb693190df76b1baef6ef98623b4f1a mcramm 1332283208 -0600 Branch: renamed refs/heads/master to refs/heads/cure_common_cold ================================================ FILE: levels/find_old_branch/.githug/logs/refs/heads/kill_the_batman ================================================ 0000000000000000000000000000000000000000 6876e5b41fb693190df76b1baef6ef98623b4f1a mcramm 1332283224 -0600 branch: Created from HEAD 6876e5b41fb693190df76b1baef6ef98623b4f1a 05e9c01bd3c9264761dd0cde477400a2c3104642 mcramm 1332283348 -0600 commit: commit todo 05e9c01bd3c9264761dd0cde477400a2c3104642 6876e5b41fb693190df76b1baef6ef98623b4f1a mcramm 1332283385 -0600 reset: moving to HEAD~ 6876e5b41fb693190df76b1baef6ef98623b4f1a 894a16d6f1a48224e9006b4a6f0fe3846da19bec mcramm 1332283505 -0600 commit: commit another todo ================================================ FILE: levels/find_old_branch/.githug/logs/refs/heads/solve_world_hunger ================================================ 0000000000000000000000000000000000000000 6876e5b41fb693190df76b1baef6ef98623b4f1a mcramm 1332283272 -0600 branch: Created from HEAD 6876e5b41fb693190df76b1baef6ef98623b4f1a 324336a8401afc8ca384eaafe6615c84d552dd2c mcramm 1332283302 -0600 commit: commit todo ================================================ FILE: levels/find_old_branch/.githug/refs/heads/blowup_sun_for_ransom ================================================ 6876e5b41fb693190df76b1baef6ef98623b4f1a ================================================ FILE: levels/find_old_branch/.githug/refs/heads/cure_common_cold ================================================ 6876e5b41fb693190df76b1baef6ef98623b4f1a ================================================ FILE: levels/find_old_branch/.githug/refs/heads/kill_the_batman ================================================ 894a16d6f1a48224e9006b4a6f0fe3846da19bec ================================================ FILE: levels/find_old_branch/.githug/refs/heads/solve_world_hunger ================================================ 324336a8401afc8ca384eaafe6615c84d552dd2c ================================================ FILE: levels/find_old_branch/TODO ================================================ FIND THE JOKER ================================================ FILE: levels/find_old_branch/myfile.txt ================================================ THIS TEXT DOESN'T MATTER ================================================ FILE: levels/find_old_branch.rb ================================================ difficulty 4 description "You have been working on a branch but got distracted by a major issue. Switch back to that branch even though you forgot the name of it." setup do init_from_level system "git branch -m master" end solution do return false unless repo.head.name == "solve_world_hunger" true end hint do puts "Ever played with the `git reflog` command?" end ================================================ FILE: levels/grep/.githug/COMMIT_EDITMSG ================================================ Add application files. ================================================ FILE: levels/grep/.githug/HEAD ================================================ ref: refs/heads/master ================================================ FILE: levels/grep/.githug/config ================================================ [core] repositoryformatversion = 0 filemode = true bare = false logallrefupdates = true ignorecase = true ================================================ FILE: levels/grep/.githug/description ================================================ Unnamed repository; edit this file 'description' to name the repository. ================================================ FILE: levels/grep/.githug/hooks/applypatch-msg.sample ================================================ #!/bin/sh # # An example hook script to check the commit log message taken by # applypatch from an e-mail message. # # The hook should exit with non-zero status after issuing an # appropriate message if it wants to stop the commit. The hook is # allowed to edit the commit message file. # # To enable this hook, rename this file to "applypatch-msg". . git-sh-setup test -x "$GIT_DIR/hooks/commit-msg" && exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"} : ================================================ FILE: levels/grep/.githug/hooks/commit-msg.sample ================================================ #!/bin/sh # # An example hook script to check the commit log message. # Called by "git commit" with one argument, the name of the file # that has the commit message. The hook should exit with non-zero # status after issuing an appropriate message if it wants to stop the # commit. The hook is allowed to edit the commit message file. # # To enable this hook, rename this file to "commit-msg". # Uncomment the below to add a Signed-off-by line to the message. # Doing this in a hook is a bad idea in general, but the prepare-commit-msg # hook is more suited to it. # # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" # This example catches duplicate Signed-off-by lines. test "" = "$(grep '^Signed-off-by: ' "$1" | sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { echo >&2 Duplicate Signed-off-by lines. exit 1 } ================================================ FILE: levels/grep/.githug/hooks/post-commit.sample ================================================ #!/bin/sh # # An example hook script that is called after a successful # commit is made. # # To enable this hook, rename this file to "post-commit". : Nothing ================================================ FILE: levels/grep/.githug/hooks/post-receive.sample ================================================ #!/bin/sh # # An example hook script for the "post-receive" event. # # The "post-receive" script is run after receive-pack has accepted a pack # and the repository has been updated. It is passed arguments in through # stdin in the form # # For example: # aa453216d1b3e49e7f6f98441fa56946ddcd6a20 68f7abf4e6f922807889f52bc043ecd31b79f814 refs/heads/master # # see contrib/hooks/ for a sample, or uncomment the next line and # rename the file to "post-receive". #. /usr/share/doc/git-core/contrib/hooks/post-receive-email ================================================ FILE: levels/grep/.githug/hooks/post-update.sample ================================================ #!/bin/sh # # An example hook script to prepare a packed repository for use over # dumb transports. # # To enable this hook, rename this file to "post-update". exec git update-server-info ================================================ FILE: levels/grep/.githug/hooks/pre-applypatch.sample ================================================ #!/bin/sh # # An example hook script to verify what is about to be committed # by applypatch from an e-mail message. # # The hook should exit with non-zero status after issuing an # appropriate message if it wants to stop the commit. # # To enable this hook, rename this file to "pre-applypatch". . git-sh-setup test -x "$GIT_DIR/hooks/pre-commit" && exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"} : ================================================ FILE: levels/grep/.githug/hooks/pre-commit.sample ================================================ #!/bin/sh # # An example hook script to verify what is about to be committed. # Called by "git commit" with no arguments. The hook should # exit with non-zero status after issuing an appropriate message if # it wants to stop the commit. # # To enable this hook, rename this file to "pre-commit". if git rev-parse --verify HEAD >/dev/null 2>&1 then against=HEAD else # Initial commit: diff against an empty tree object against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 fi # If you want to allow non-ascii filenames set this variable to true. allownonascii=$(git config hooks.allownonascii) # Cross platform projects tend to avoid non-ascii filenames; prevent # them from being added to the repository. We exploit the fact that the # printable range starts at the space character and ends with tilde. if [ "$allownonascii" != "true" ] && # Note that the use of brackets around a tr range is ok here, (it's # even required, for portability to Solaris 10's /usr/bin/tr), since # the square bracket bytes happen to fall in the designated range. test "$(git diff --cached --name-only --diff-filter=A -z $against | LC_ALL=C tr -d '[ -~]\0')" then echo "Error: Attempt to add a non-ascii file name." echo echo "This can cause problems if you want to work" echo "with people on other platforms." echo echo "To be portable it is advisable to rename the file ..." echo echo "If you know what you are doing you can disable this" echo "check using:" echo echo " git config hooks.allownonascii true" echo exit 1 fi exec git diff-index --check --cached $against -- ================================================ FILE: levels/grep/.githug/hooks/pre-rebase.sample ================================================ #!/bin/sh # # Copyright (c) 2006, 2008 Junio C Hamano # # The "pre-rebase" hook is run just before "git rebase" starts doing # its job, and can prevent the command from running by exiting with # non-zero status. # # The hook is called with the following parameters: # # $1 -- the upstream the series was forked from. # $2 -- the branch being rebased (or empty when rebasing the current branch). # # This sample shows how to prevent topic branches that are already # merged to 'next' branch from getting rebased, because allowing it # would result in rebasing already published history. publish=next basebranch="$1" if test "$#" = 2 then topic="refs/heads/$2" else topic=`git symbolic-ref HEAD` || exit 0 ;# we do not interrupt rebasing detached HEAD fi case "$topic" in refs/heads/??/*) ;; *) exit 0 ;# we do not interrupt others. ;; esac # Now we are dealing with a topic branch being rebased # on top of master. Is it OK to rebase it? # Does the topic really exist? git show-ref -q "$topic" || { echo >&2 "No such branch $topic" exit 1 } # Is topic fully merged to master? not_in_master=`git rev-list --pretty=oneline ^master "$topic"` if test -z "$not_in_master" then echo >&2 "$topic is fully merged to master; better remove it." exit 1 ;# we could allow it, but there is no point. fi # Is topic ever merged to next? If so you should not be rebasing it. only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` only_next_2=`git rev-list ^master ${publish} | sort` if test "$only_next_1" = "$only_next_2" then not_in_topic=`git rev-list "^$topic" master` if test -z "$not_in_topic" then echo >&2 "$topic is already up-to-date with master" exit 1 ;# we could allow it, but there is no point. else exit 0 fi else not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` /usr/bin/perl -e ' my $topic = $ARGV[0]; my $msg = "* $topic has commits already merged to public branch:\n"; my (%not_in_next) = map { /^([0-9a-f]+) /; ($1 => 1); } split(/\n/, $ARGV[1]); for my $elem (map { /^([0-9a-f]+) (.*)$/; [$1 => $2]; } split(/\n/, $ARGV[2])) { if (!exists $not_in_next{$elem->[0]}) { if ($msg) { print STDERR $msg; undef $msg; } print STDERR " $elem->[1]\n"; } } ' "$topic" "$not_in_next" "$not_in_master" exit 1 fi exit 0 ################################################################ This sample hook safeguards topic branches that have been published from being rewound. The workflow assumed here is: * Once a topic branch forks from "master", "master" is never merged into it again (either directly or indirectly). * Once a topic branch is fully cooked and merged into "master", it is deleted. If you need to build on top of it to correct earlier mistakes, a new topic branch is created by forking at the tip of the "master". This is not strictly necessary, but it makes it easier to keep your history simple. * Whenever you need to test or publish your changes to topic branches, merge them into "next" branch. The script, being an example, hardcodes the publish branch name to be "next", but it is trivial to make it configurable via $GIT_DIR/config mechanism. With this workflow, you would want to know: (1) ... if a topic branch has ever been merged to "next". Young topic branches can have stupid mistakes you would rather clean up before publishing, and things that have not been merged into other branches can be easily rebased without affecting other people. But once it is published, you would not want to rewind it. (2) ... if a topic branch has been fully merged to "master". Then you can delete it. More importantly, you should not build on top of it -- other people may already want to change things related to the topic as patches against your "master", so if you need further changes, it is better to fork the topic (perhaps with the same name) afresh from the tip of "master". Let's look at this example: o---o---o---o---o---o---o---o---o---o "next" / / / / / a---a---b A / / / / / / / / c---c---c---c B / / / / \ / / / / b---b C \ / / / / / \ / ---o---o---o---o---o---o---o---o---o---o---o "master" A, B and C are topic branches. * A has one fix since it was merged up to "next". * B has finished. It has been fully merged up to "master" and "next", and is ready to be deleted. * C has not merged to "next" at all. We would want to allow C to be rebased, refuse A, and encourage B to be deleted. To compute (1): git rev-list ^master ^topic next git rev-list ^master next if these match, topic has not merged in next at all. To compute (2): git rev-list master..topic if this is empty, it is fully merged to "master". ================================================ FILE: levels/grep/.githug/hooks/prepare-commit-msg.sample ================================================ #!/bin/sh # # An example hook script to prepare the commit log message. # Called by "git commit" with the name of the file that has the # commit message, followed by the description of the commit # message's source. The hook's purpose is to edit the commit # message file. If the hook fails with a non-zero status, # the commit is aborted. # # To enable this hook, rename this file to "prepare-commit-msg". # This hook includes three examples. The first comments out the # "Conflicts:" part of a merge commit. # # The second includes the output of "git diff --name-status -r" # into the message, just before the "git status" output. It is # commented because it doesn't cope with --amend or with squashed # commits. # # The third example adds a Signed-off-by line to the message, that can # still be edited. This is rarely a good idea. case "$2,$3" in merge,) /usr/bin/perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;; # ,|template,) # /usr/bin/perl -i.bak -pe ' # print "\n" . `git diff --cached --name-status -r` # if /^#/ && $first++ == 0' "$1" ;; *) ;; esac # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" ================================================ FILE: levels/grep/.githug/hooks/update.sample ================================================ #!/bin/sh # # An example hook script to blocks unannotated tags from entering. # Called by "git receive-pack" with arguments: refname sha1-old sha1-new # # To enable this hook, rename this file to "update". # # Config # ------ # hooks.allowunannotated # This boolean sets whether unannotated tags will be allowed into the # repository. By default they won't be. # hooks.allowdeletetag # This boolean sets whether deleting tags will be allowed in the # repository. By default they won't be. # hooks.allowmodifytag # This boolean sets whether a tag may be modified after creation. By default # it won't be. # hooks.allowdeletebranch # This boolean sets whether deleting branches will be allowed in the # repository. By default they won't be. # hooks.denycreatebranch # This boolean sets whether remotely creating branches will be denied # in the repository. By default this is allowed. # # --- Command line refname="$1" oldrev="$2" newrev="$3" # --- Safety check if [ -z "$GIT_DIR" ]; then echo "Don't run this script from the command line." >&2 echo " (if you want, you could supply GIT_DIR then run" >&2 echo " $0 )" >&2 exit 1 fi if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then echo "Usage: $0 " >&2 exit 1 fi # --- Config allowunannotated=$(git config --bool hooks.allowunannotated) allowdeletebranch=$(git config --bool hooks.allowdeletebranch) denycreatebranch=$(git config --bool hooks.denycreatebranch) allowdeletetag=$(git config --bool hooks.allowdeletetag) allowmodifytag=$(git config --bool hooks.allowmodifytag) # check for no description projectdesc=$(sed -e '1q' "$GIT_DIR/description") case "$projectdesc" in "Unnamed repository"* | "") echo "*** Project description file hasn't been set" >&2 exit 1 ;; esac # --- Check types # if $newrev is 0000...0000, it's a commit to delete a ref. zero="0000000000000000000000000000000000000000" if [ "$newrev" = "$zero" ]; then newrev_type=delete else newrev_type=$(git cat-file -t $newrev) fi case "$refname","$newrev_type" in refs/tags/*,commit) # un-annotated tag short_refname=${refname##refs/tags/} if [ "$allowunannotated" != "true" ]; then echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2 echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 exit 1 fi ;; refs/tags/*,delete) # delete tag if [ "$allowdeletetag" != "true" ]; then echo "*** Deleting a tag is not allowed in this repository" >&2 exit 1 fi ;; refs/tags/*,tag) # annotated tag if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 then echo "*** Tag '$refname' already exists." >&2 echo "*** Modifying a tag is not allowed in this repository." >&2 exit 1 fi ;; refs/heads/*,commit) # branch if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then echo "*** Creating a branch is not allowed in this repository" >&2 exit 1 fi ;; refs/heads/*,delete) # delete branch if [ "$allowdeletebranch" != "true" ]; then echo "*** Deleting a branch is not allowed in this repository" >&2 exit 1 fi ;; refs/remotes/*,commit) # tracking branch ;; refs/remotes/*,delete) # delete tracking branch if [ "$allowdeletebranch" != "true" ]; then echo "*** Deleting a tracking branch is not allowed in this repository" >&2 exit 1 fi ;; *) # Anything else (is there anything else?) echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 exit 1 ;; esac # --- Finished exit 0 ================================================ FILE: levels/grep/.githug/info/exclude ================================================ # git ls-files --others --exclude-from=.git/info/exclude # Lines that start with '#' are comments. # For a project mostly in C, the following would be a good set of # exclude patterns (uncomment them if you want to use them): # *.[oa] # *~ ================================================ FILE: levels/grep/.githug/logs/HEAD ================================================ 0000000000000000000000000000000000000000 12c702f8b25b6b528cf904670b854dba3eba0f45 Thibaud Colas 1384229258 +0100 commit (initial): Add application files. ================================================ FILE: levels/grep/.githug/logs/refs/heads/master ================================================ 0000000000000000000000000000000000000000 12c702f8b25b6b528cf904670b854dba3eba0f45 Thibaud Colas 1384229258 +0100 commit (initial): Add application files. ================================================ FILE: levels/grep/.githug/objects/12/c702f8b25b6b528cf904670b854dba3eba0f45 ================================================ xK 0@]JЀdj)mz+{՚;O}f[1:Dޙ,D)rd}n+Eg,ن8!dB}FXQ]lŧoJkrNViL/M*ec f=u$`X+s|>#\u/PiQq*6G\dYQ&%4jh)}N)QlGmh ================================================ FILE: levels/grep/.githug/refs/heads/master ================================================ 12c702f8b25b6b528cf904670b854dba3eba0f45 ================================================ FILE: levels/grep/app.rb ================================================ require 'sinatra' require 'oauth2' require 'json' enable :sessions # TODO Make site url variable. def client OAuth2::Client.new("mTeZFqkCmzc8JnjKXaSww95bFFxhUpp1wwmSi8vG", "a9OMyEdW7JvWThHmmvFcShR9P2dyad3EGuA2ULDh", :site => "http://localhost:3000") end get "/auth/test" do redirect client.auth_code.authorize_url(:redirect_uri => redirect_uri) end get '/auth/test/callback' do access_token = client.auth_code.get_token(params[:code], :redirect_uri => redirect_uri) session[:access_token] = access_token.token @message = "Successfully authenticated with the server" erb :success end get '/yet_another' do @message = get_response('data.json') erb :success end get '/another_page' do @message = get_response('server.json') erb :another end # TODO Make API version variable. def get_response(url) access_token = OAuth2::AccessToken.new(client, session[:access_token]) JSON.parse(access_token.get("/api/v1/#{url}").body) end # TODO Redirecting queries could be useful. def redirect_uri uri = URI.parse(request.url) uri.path = '/auth/test/callback' uri.query = nil uri.to_s end ================================================ FILE: levels/grep/config.rb ================================================ class Config attr_accessor :name, :password def initialize(name, password = nil, options = {}) @name = name # TODO Move password to a configuration file. @password = password || "i<3evil" if options[:downcase] @name.downcase! end if options[:upcase] @name.upcase! end end end ================================================ FILE: levels/grep.rb ================================================ difficulty 2 description "Your project's deadline approaches, you should evaluate how many TODOs are left in your code" setup do init_from_level system "git branch -m master" end solution do request("How many items are there in your todolist?") == "4" end hint do puts "You want to research the `git grep` command." end ================================================ FILE: levels/ignore.rb ================================================ difficulty 2 description "The text editor 'vim' creates files ending in `.swp` (swap files) for all files that are currently open. We don't want them creeping into the repository. Make this repository ignore those swap files which are ending in `.swp`." setup do repo.init FileUtils.touch("README.swp") system "git branch -m master" file = File.open(".git/config", "w") do |file| file.puts "[core]\nexcludesfile=" end end solution do valid = false File.open(".gitignore", "r") do |file| while line = file.gets if line.chomp == "*.swp" valid = true end end end valid end hint do puts "You may have noticed there is a file named `.gitignore` in the repository." end ================================================ FILE: levels/include.rb ================================================ difficulty 2 description "Notice a few files with the '.a' extension. We want git to ignore all the files except the 'lib.a' file." setup do repo.init FileUtils.touch("first.a") FileUtils.touch("second.a") FileUtils.touch("lib.a") system "git branch -m master" file = File.open(".git/config", "w") do |file| file.puts "[core]\nexcludesfile=" end end solution do entries = File.readlines('.gitignore').map(&:chomp) entries.include? '*.a' and entries.include? '!lib.a' end hint do puts "Using `git help ignore`, read about the optional prefix to negate a pattern." end ================================================ FILE: levels/init.rb ================================================ difficulty 1 description "A new directory, `git_hug`, has been created; initialize an empty repository in it." solution do repo.valid? end hint do puts "You can type `git --help` or `git` in your shell to get a list of available git commands." end ================================================ FILE: levels/log.rb ================================================ difficulty 2 description "Identify the hash of the latest commit." setup do repo.init file = File.new("newfile.rb", "w") repo.add("newfile.rb") repo.commit_all("THIS IS THE COMMIT YOU ARE LOOKING FOR!") system "git branch -m master" end solution do repo.commits.last.id_abbrev == request("What is the hash of the most recent commit?")[0..6] end hint do puts "You need to investigate the logs. There is probably a command for doing that!" end ================================================ FILE: levels/merge/.githug/COMMIT_EDITMSG ================================================ added file2 ================================================ FILE: levels/merge/.githug/HEAD ================================================ ref: refs/heads/master ================================================ FILE: levels/merge/.githug/config ================================================ [core] repositoryformatversion = 0 filemode = true bare = false logallrefupdates = true ignorecase = true ================================================ FILE: levels/merge/.githug/description ================================================ Unnamed repository; edit this file 'description' to name the repository. ================================================ FILE: levels/merge/.githug/hooks/applypatch-msg.sample ================================================ #!/bin/sh # # An example hook script to check the commit log message taken by # applypatch from an e-mail message. # # The hook should exit with non-zero status after issuing an # appropriate message if it wants to stop the commit. The hook is # allowed to edit the commit message file. # # To enable this hook, rename this file to "applypatch-msg". . git-sh-setup test -x "$GIT_DIR/hooks/commit-msg" && exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"} : ================================================ FILE: levels/merge/.githug/hooks/commit-msg.sample ================================================ #!/bin/sh # # An example hook script to check the commit log message. # Called by "git commit" with one argument, the name of the file # that has the commit message. The hook should exit with non-zero # status after issuing an appropriate message if it wants to stop the # commit. The hook is allowed to edit the commit message file. # # To enable this hook, rename this file to "commit-msg". # Uncomment the below to add a Signed-off-by line to the message. # Doing this in a hook is a bad idea in general, but the prepare-commit-msg # hook is more suited to it. # # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" # This example catches duplicate Signed-off-by lines. test "" = "$(grep '^Signed-off-by: ' "$1" | sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { echo >&2 Duplicate Signed-off-by lines. exit 1 } ================================================ FILE: levels/merge/.githug/hooks/post-commit.sample ================================================ #!/bin/sh # # An example hook script that is called after a successful # commit is made. # # To enable this hook, rename this file to "post-commit". : Nothing ================================================ FILE: levels/merge/.githug/hooks/post-receive.sample ================================================ #!/bin/sh # # An example hook script for the "post-receive" event. # # The "post-receive" script is run after receive-pack has accepted a pack # and the repository has been updated. It is passed arguments in through # stdin in the form # # For example: # aa453216d1b3e49e7f6f98441fa56946ddcd6a20 68f7abf4e6f922807889f52bc043ecd31b79f814 refs/heads/master # # see contrib/hooks/ for a sample, or uncomment the next line and # rename the file to "post-receive". #. /usr/share/doc/git-core/contrib/hooks/post-receive-email ================================================ FILE: levels/merge/.githug/hooks/post-update.sample ================================================ #!/bin/sh # # An example hook script to prepare a packed repository for use over # dumb transports. # # To enable this hook, rename this file to "post-update". exec git update-server-info ================================================ FILE: levels/merge/.githug/hooks/pre-applypatch.sample ================================================ #!/bin/sh # # An example hook script to verify what is about to be committed # by applypatch from an e-mail message. # # The hook should exit with non-zero status after issuing an # appropriate message if it wants to stop the commit. # # To enable this hook, rename this file to "pre-applypatch". . git-sh-setup test -x "$GIT_DIR/hooks/pre-commit" && exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"} : ================================================ FILE: levels/merge/.githug/hooks/pre-commit.sample ================================================ #!/bin/sh # # An example hook script to verify what is about to be committed. # Called by "git commit" with no arguments. The hook should # exit with non-zero status after issuing an appropriate message if # it wants to stop the commit. # # To enable this hook, rename this file to "pre-commit". if git rev-parse --verify HEAD >/dev/null 2>&1 then against=HEAD else # Initial commit: diff against an empty tree object against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 fi # If you want to allow non-ascii filenames set this variable to true. allownonascii=$(git config hooks.allownonascii) # Cross platform projects tend to avoid non-ascii filenames; prevent # them from being added to the repository. We exploit the fact that the # printable range starts at the space character and ends with tilde. if [ "$allownonascii" != "true" ] && # Note that the use of brackets around a tr range is ok here, (it's # even required, for portability to Solaris 10's /usr/bin/tr), since # the square bracket bytes happen to fall in the designated range. test "$(git diff --cached --name-only --diff-filter=A -z $against | LC_ALL=C tr -d '[ -~]\0')" then echo "Error: Attempt to add a non-ascii file name." echo echo "This can cause problems if you want to work" echo "with people on other platforms." echo echo "To be portable it is advisable to rename the file ..." echo echo "If you know what you are doing you can disable this" echo "check using:" echo echo " git config hooks.allownonascii true" echo exit 1 fi exec git diff-index --check --cached $against -- ================================================ FILE: levels/merge/.githug/hooks/pre-rebase.sample ================================================ #!/bin/sh # # Copyright (c) 2006, 2008 Junio C Hamano # # The "pre-rebase" hook is run just before "git rebase" starts doing # its job, and can prevent the command from running by exiting with # non-zero status. # # The hook is called with the following parameters: # # $1 -- the upstream the series was forked from. # $2 -- the branch being rebased (or empty when rebasing the current branch). # # This sample shows how to prevent topic branches that are already # merged to 'next' branch from getting rebased, because allowing it # would result in rebasing already published history. publish=next basebranch="$1" if test "$#" = 2 then topic="refs/heads/$2" else topic=`git symbolic-ref HEAD` || exit 0 ;# we do not interrupt rebasing detached HEAD fi case "$topic" in refs/heads/??/*) ;; *) exit 0 ;# we do not interrupt others. ;; esac # Now we are dealing with a topic branch being rebased # on top of master. Is it OK to rebase it? # Does the topic really exist? git show-ref -q "$topic" || { echo >&2 "No such branch $topic" exit 1 } # Is topic fully merged to master? not_in_master=`git rev-list --pretty=oneline ^master "$topic"` if test -z "$not_in_master" then echo >&2 "$topic is fully merged to master; better remove it." exit 1 ;# we could allow it, but there is no point. fi # Is topic ever merged to next? If so you should not be rebasing it. only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` only_next_2=`git rev-list ^master ${publish} | sort` if test "$only_next_1" = "$only_next_2" then not_in_topic=`git rev-list "^$topic" master` if test -z "$not_in_topic" then echo >&2 "$topic is already up-to-date with master" exit 1 ;# we could allow it, but there is no point. else exit 0 fi else not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` /opt/local/bin/perl -e ' my $topic = $ARGV[0]; my $msg = "* $topic has commits already merged to public branch:\n"; my (%not_in_next) = map { /^([0-9a-f]+) /; ($1 => 1); } split(/\n/, $ARGV[1]); for my $elem (map { /^([0-9a-f]+) (.*)$/; [$1 => $2]; } split(/\n/, $ARGV[2])) { if (!exists $not_in_next{$elem->[0]}) { if ($msg) { print STDERR $msg; undef $msg; } print STDERR " $elem->[1]\n"; } } ' "$topic" "$not_in_next" "$not_in_master" exit 1 fi exit 0 ################################################################ This sample hook safeguards topic branches that have been published from being rewound. The workflow assumed here is: * Once a topic branch forks from "master", "master" is never merged into it again (either directly or indirectly). * Once a topic branch is fully cooked and merged into "master", it is deleted. If you need to build on top of it to correct earlier mistakes, a new topic branch is created by forking at the tip of the "master". This is not strictly necessary, but it makes it easier to keep your history simple. * Whenever you need to test or publish your changes to topic branches, merge them into "next" branch. The script, being an example, hardcodes the publish branch name to be "next", but it is trivial to make it configurable via $GIT_DIR/config mechanism. With this workflow, you would want to know: (1) ... if a topic branch has ever been merged to "next". Young topic branches can have stupid mistakes you would rather clean up before publishing, and things that have not been merged into other branches can be easily rebased without affecting other people. But once it is published, you would not want to rewind it. (2) ... if a topic branch has been fully merged to "master". Then you can delete it. More importantly, you should not build on top of it -- other people may already want to change things related to the topic as patches against your "master", so if you need further changes, it is better to fork the topic (perhaps with the same name) afresh from the tip of "master". Let's look at this example: o---o---o---o---o---o---o---o---o---o "next" / / / / / a---a---b A / / / / / / / / c---c---c---c B / / / / \ / / / / b---b C \ / / / / / \ / ---o---o---o---o---o---o---o---o---o---o---o "master" A, B and C are topic branches. * A has one fix since it was merged up to "next". * B has finished. It has been fully merged up to "master" and "next", and is ready to be deleted. * C has not merged to "next" at all. We would want to allow C to be rebased, refuse A, and encourage B to be deleted. To compute (1): git rev-list ^master ^topic next git rev-list ^master next if these match, topic has not merged in next at all. To compute (2): git rev-list master..topic if this is empty, it is fully merged to "master". ================================================ FILE: levels/merge/.githug/hooks/prepare-commit-msg.sample ================================================ #!/bin/sh # # An example hook script to prepare the commit log message. # Called by "git commit" with the name of the file that has the # commit message, followed by the description of the commit # message's source. The hook's purpose is to edit the commit # message file. If the hook fails with a non-zero status, # the commit is aborted. # # To enable this hook, rename this file to "prepare-commit-msg". # This hook includes three examples. The first comments out the # "Conflicts:" part of a merge commit. # # The second includes the output of "git diff --name-status -r" # into the message, just before the "git status" output. It is # commented because it doesn't cope with --amend or with squashed # commits. # # The third example adds a Signed-off-by line to the message, that can # still be edited. This is rarely a good idea. case "$2,$3" in merge,) /opt/local/bin/perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;; # ,|template,) # /opt/local/bin/perl -i.bak -pe ' # print "\n" . `git diff --cached --name-status -r` # if /^#/ && $first++ == 0' "$1" ;; *) ;; esac # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" ================================================ FILE: levels/merge/.githug/hooks/update.sample ================================================ #!/bin/sh # # An example hook script to blocks unannotated tags from entering. # Called by "git receive-pack" with arguments: refname sha1-old sha1-new # # To enable this hook, rename this file to "update". # # Config # ------ # hooks.allowunannotated # This boolean sets whether unannotated tags will be allowed into the # repository. By default they won't be. # hooks.allowdeletetag # This boolean sets whether deleting tags will be allowed in the # repository. By default they won't be. # hooks.allowmodifytag # This boolean sets whether a tag may be modified after creation. By default # it won't be. # hooks.allowdeletebranch # This boolean sets whether deleting branches will be allowed in the # repository. By default they won't be. # hooks.denycreatebranch # This boolean sets whether remotely creating branches will be denied # in the repository. By default this is allowed. # # --- Command line refname="$1" oldrev="$2" newrev="$3" # --- Safety check if [ -z "$GIT_DIR" ]; then echo "Don't run this script from the command line." >&2 echo " (if you want, you could supply GIT_DIR then run" >&2 echo " $0 )" >&2 exit 1 fi if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then echo "Usage: $0 " >&2 exit 1 fi # --- Config allowunannotated=$(git config --bool hooks.allowunannotated) allowdeletebranch=$(git config --bool hooks.allowdeletebranch) denycreatebranch=$(git config --bool hooks.denycreatebranch) allowdeletetag=$(git config --bool hooks.allowdeletetag) allowmodifytag=$(git config --bool hooks.allowmodifytag) # check for no description projectdesc=$(sed -e '1q' "$GIT_DIR/description") case "$projectdesc" in "Unnamed repository"* | "") echo "*** Project description file hasn't been set" >&2 exit 1 ;; esac # --- Check types # if $newrev is 0000...0000, it's a commit to delete a ref. zero="0000000000000000000000000000000000000000" if [ "$newrev" = "$zero" ]; then newrev_type=delete else newrev_type=$(git cat-file -t $newrev) fi case "$refname","$newrev_type" in refs/tags/*,commit) # un-annotated tag short_refname=${refname##refs/tags/} if [ "$allowunannotated" != "true" ]; then echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2 echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 exit 1 fi ;; refs/tags/*,delete) # delete tag if [ "$allowdeletetag" != "true" ]; then echo "*** Deleting a tag is not allowed in this repository" >&2 exit 1 fi ;; refs/tags/*,tag) # annotated tag if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 then echo "*** Tag '$refname' already exists." >&2 echo "*** Modifying a tag is not allowed in this repository." >&2 exit 1 fi ;; refs/heads/*,commit) # branch if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then echo "*** Creating a branch is not allowed in this repository" >&2 exit 1 fi ;; refs/heads/*,delete) # delete branch if [ "$allowdeletebranch" != "true" ]; then echo "*** Deleting a branch is not allowed in this repository" >&2 exit 1 fi ;; refs/remotes/*,commit) # tracking branch ;; refs/remotes/*,delete) # delete tracking branch if [ "$allowdeletebranch" != "true" ]; then echo "*** Deleting a tracking branch is not allowed in this repository" >&2 exit 1 fi ;; *) # Anything else (is there anything else?) echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 exit 1 ;; esac # --- Finished exit 0 ================================================ FILE: levels/merge/.githug/info/exclude ================================================ # git ls-files --others --exclude-from=.git/info/exclude # Lines that start with '#' are comments. # For a project mostly in C, the following would be a good set of # exclude patterns (uncomment them if you want to use them): # *.[oa] # *~ ================================================ FILE: levels/merge/.githug/logs/HEAD ================================================ 0000000000000000000000000000000000000000 e12277fe88657a072f1c4eb7d9320e4e6a74ba95 Dustin Rodrigues 1331884867 -0700 commit (initial): added file1 e12277fe88657a072f1c4eb7d9320e4e6a74ba95 e12277fe88657a072f1c4eb7d9320e4e6a74ba95 Dustin Rodrigues 1331884877 -0700 checkout: moving from master to feature e12277fe88657a072f1c4eb7d9320e4e6a74ba95 cc8ea5a233df119d025eb240b9470e1ca76a151c Dustin Rodrigues 1331884901 -0700 commit: added file2 cc8ea5a233df119d025eb240b9470e1ca76a151c e12277fe88657a072f1c4eb7d9320e4e6a74ba95 Dustin Rodrigues 1331884911 -0700 checkout: moving from feature to master ================================================ FILE: levels/merge/.githug/logs/refs/heads/feature ================================================ 0000000000000000000000000000000000000000 e12277fe88657a072f1c4eb7d9320e4e6a74ba95 Dustin Rodrigues 1331884877 -0700 branch: Created from HEAD e12277fe88657a072f1c4eb7d9320e4e6a74ba95 cc8ea5a233df119d025eb240b9470e1ca76a151c Dustin Rodrigues 1331884901 -0700 commit: added file2 ================================================ FILE: levels/merge/.githug/logs/refs/heads/master ================================================ 0000000000000000000000000000000000000000 e12277fe88657a072f1c4eb7d9320e4e6a74ba95 Dustin Rodrigues 1331884867 -0700 commit (initial): added file1 ================================================ FILE: levels/merge/.githug/refs/heads/feature ================================================ cc8ea5a233df119d025eb240b9470e1ca76a151c ================================================ FILE: levels/merge/.githug/refs/heads/master ================================================ e12277fe88657a072f1c4eb7d9320e4e6a74ba95 ================================================ FILE: levels/merge/file1 ================================================ ================================================ FILE: levels/merge.rb ================================================ difficulty 2 description "We have a file in the branch 'feature'. Let's merge it with the master branch." setup do init_from_level system "git branch -m master" end solution do File.exists?("file1") && File.exists?("file2") end hint do puts "You want to research the `git merge` command." end ================================================ FILE: levels/merge_squash.rb ================================================ difficulty 3 description "Merge all commits from the long-feature-branch as a single commit." setup do repo.init FileUtils.touch "file1" repo.add "file1" repo.commit_all "First commit" system "git branch -m master" repo.git.native :checkout, {"b" => true}, 'long-feature-branch' File.open("file3", 'w') { |f| f << "some feature\n" } repo.add "file3" repo.commit_all "Developing new features" File.open("file3", 'a') { |f| f << "getting awesomer\n" } repo.add "file3" repo.commit_all "Takes" File.open("file3", 'a') { |f| f << "and awesomer!\n" } repo.add "file3" repo.commit_all "Time" repo.git.native :checkout, {}, 'master' FileUtils.touch "file2" repo.add "file2" repo.commit_all "Second commit" end solution do result = true # Check the number of commits in the repo (should be 4 - including initial .gitignore). result = false unless repo.commits.size == 3 # Check if changes from all the commits from long-feature-branch are included. file = File.open('file3') result = false unless file.readline =~ /some feature/ result = false unless file.readline =~ /getting awesomer/ result = false unless file.readline =~ /and awesomer!/ file.close result end hint do puts "Take a look at the `--squash` option of the merge command. Don't forget to commit the merge!" end ================================================ FILE: levels/number_of_files_committed.rb ================================================ difficulty 1 description "There are some files in this repository; how many of them are staged for a commit?" setup do repo.init # Modified files %w{rubyfile4.rb rubyfile5.rb}.each do |file| FileUtils.touch(file) repo.add(file) system "git branch -m master" end repo.commit_all "Commit" # Staged file File.open("rubyfile4.rb", 'w') { |f| f << "#Changes" } repo.add("rubyfile4.rb") # Not staged file File.open("rubyfile5.rb", 'w') { |f| f << "#Changes" } # Changes to be committed %w{rubyfile1.rb}.each do |file| FileUtils.touch(file) repo.add(file) end # Untracked files %w{rubyfile6.rb rubyfile7.rb}.each do |file| FileUtils.touch(file) end end solution do numberOfFilesThereWillBeCommit = request("How many files are going to be committed?") isInteger = !!(numberOfFilesThereWillBeCommit =~ /^[-+]?[0-9]+$/) if !isInteger return false end if numberOfFilesThereWillBeCommit.to_i == 2 return true end return false end hint do puts "You are looking for a command to identify the status of the repository (resembles a Linux command)." end ================================================ FILE: levels/pull.rb ================================================ difficulty 2 description "You need to pull changes from your origin repository." setup do repo.init repo.remote_add("origin", "https://github.com/pull-this/thing-to-pull") system "git branch -m master" end solution do repo.commits.last.id_abbrev == "1797a7c" end hint do puts "Check out the remote repositories and research `git pull`." end ================================================ FILE: levels/push.rb ================================================ difficulty 3 description "Your local master branch has diverged from " + "the remote origin/master branch. Rebase your branch onto " + "origin/master and push it to remote." setup do # remember the working directory so we can come back to it later cwd = Dir.pwd # initialize another git repo to be used as a "remote" tmpdir = Dir.mktmpdir # local repo repo.init FileUtils.touch "file1" repo.add "file1" repo.commit_all "First commit" FileUtils.touch "file2" repo.add "file2" repo.commit_all "Second commit" # copy the repo to remote FileUtils.cp "file1", tmpdir FileUtils.cp "file2", tmpdir # add another file FileUtils.touch "file3" repo.add "file3" repo.commit_all "Third commit" system "git branch -m master" # remote repo Dir.chdir tmpdir repo.init # make a 'non-bare' repo accept pushes `git config receive.denyCurrentBranch ignore` # add a different file and commit so remote and local would diverge FileUtils.touch "file4" repo.add "file4" repo.commit_all "Fourth commit" system "git branch -m master" # tentative addition # change back to original repo to set up a remote Dir.chdir cwd `git remote add origin #{tmpdir}/.git` `git fetch origin` `git branch -u origin/master master 2> /dev/null` end solution do repo.init result = true # Check the commits of the local branch and the branch are the same. local_commits = repo.commits("master") remote_commits = repo.commits("origin/master") result = false unless local_commits.size == 4 local_commits.each_with_index do |commit, idx| result &&= (commit.id == remote_commits[idx].id) end result end hint do puts "Take a look at `git fetch`, `git pull`, and `git push`." end ================================================ FILE: levels/push_branch.rb ================================================ difficulty 2 description "You've made some changes to a local branch and want to share it, but aren't yet ready to merge it with the 'master' branch. Push only 'test_branch' to the remote repository" setup do # remember the working directory so we can come back to it later cwd = Dir.pwd # initialize another git repo to be used as a "remote" tmpdir = Dir.mktmpdir # local repo repo.init FileUtils.touch "file1" repo.add "file1" repo.commit_all "committed changes on master" system "git branch -m master" # copy the repo to remote FileUtils.cp_r ".", tmpdir # add another file. If successful this file won't be pushed to the remote repository FileUtils.touch "file2" repo.add "file2" repo.commit_all "If this commit gets pushed to repo, then you have lost the level :( " # This branch should not be pushed to to the remote repository `git checkout -b other_branch --quiet` # add another file FileUtils.touch "file3" repo.add "file3" repo.commit_all "If this commit gets pushed to repo, then you have lost the level :( " `git checkout -b test_branch --quiet` # This file should get pushed if the level is successful FileUtils.touch "file4" repo.add "file4" repo.commit_all "committed change on test_branch" # remote repo Dir.chdir tmpdir repo.init # make a 'non-bare' repo accept pushes `git config receive.denyCurrentBranch ignore` # change back to original repo to set up a remote Dir.chdir cwd `git remote add origin #{tmpdir}/.git` `git fetch --quiet origin` `git branch -u origin/master master 2> /dev/null` `git checkout master --quiet` # return to master branch end solution do repo.init result = false # each branch consists of one line, `wc -l` counts the number of lines in order to get the number of remote branches # At the moment Grit doesn't support remote branch references but is on the ToDo list. This should be revisited when Grit implements the change num_remote_branches = `git branch -r`.split("\n").count # counts the number of commits in the remote master branch' remote_master_commits = repo.commits('origin/master').count remote_test_branch_commits = repo.commits('origin/test_branch').count # if returns 0 indicates that the remote test_branch doesn't exist # Level will be successful if the remote master branch remains at 1 commit, the remote test_branch and only 2 remote branches exists if remote_master_commits == 1 and remote_test_branch_commits > 0 and num_remote_branches == 2 result = true # User pushed up too many branches, level failed elsif num_remote_branches > 2 puts "*** It looks like you pushed up too many branches. You need to make sure only 'test_branch' gets pushed. Please try again! ***" # User pushed up the master branch, level failed elsif remote_master_commits > 1 puts "*** It looks like you pushed up new master branch changes. You need to make sure only 'test_branch' gets pushed. Please try again! ***" end result end hint do puts "Investigate the options in `git push` using `git push --help`" end ================================================ FILE: levels/push_tags.rb ================================================ difficulty 2 description "A tag in the local repository isn't pushed into remote repository. Push it now." setup do # remember the working directory so we can come back to it later cwd = Dir.pwd # initialize another git repo to be used as a "remote" tmpdir = Dir.mktmpdir # local repo repo.init FileUtils.touch "file1" repo.add "file1" repo.commit_all "First commit" repo.git.tag({'f' => true}, "tag_to_be_pushed") system "git branch -m master" FileUtils.touch "file2" repo.add "file2" repo.commit_all "Second commit" # copy the repo to remote FileUtils.cp_r ".", tmpdir # remote repo Dir.chdir tmpdir repo.init # make a 'non-bare' repo accept pushes `git config receive.denyCurrentBranch ignore` # change back to original repo to set up a remote Dir.chdir cwd `git remote add origin #{tmpdir}/.git` `git fetch origin` # delete tags from remote Dir.chdir tmpdir repo.git.tag({'d' => true}, "tag_to_be_pushed") # change back to local repo Dir.chdir cwd end solution do solved = false # a bit hacky solution to get tags from remote remote_tags= repo.git.raw_git_call("git ls-remote --tags .", repo.git.git_file_index). first. split("\n") # see if we have the correct tag in the remote remote_tags.each do |t| solved=true if t.include?("refs/tags/tag_to_be_pushed") end solved end hint do puts "Take a look at `--tags` flag of `git push`" end ================================================ FILE: levels/rebase/.githug/COMMIT_EDITMSG ================================================ add content ================================================ FILE: levels/rebase/.githug/HEAD ================================================ ref: refs/heads/master ================================================ FILE: levels/rebase/.githug/ORIG_HEAD ================================================ 4419b972c0cd1b346ac90332aa7c5cc949589f78 ================================================ FILE: levels/rebase/.githug/config ================================================ [core] repositoryformatversion = 0 filemode = true bare = false logallrefupdates = true ignorecase = true precomposeunicode = true ================================================ FILE: levels/rebase/.githug/logs/HEAD ================================================ 0000000000000000000000000000000000000000 a78bcab6232e9382a86436cdfcb2ed0391b1f0ac ipmsteven 1418373185 -0800 commit (initial): init commit a78bcab6232e9382a86436cdfcb2ed0391b1f0ac a78bcab6232e9382a86436cdfcb2ed0391b1f0ac ipmsteven 1418373194 -0800 checkout: moving from master to feature a78bcab6232e9382a86436cdfcb2ed0391b1f0ac ed0fdcf366b21b8984fb37ea34106978a2e5c5ba ipmsteven 1418373239 -0800 commit: add feature ed0fdcf366b21b8984fb37ea34106978a2e5c5ba a78bcab6232e9382a86436cdfcb2ed0391b1f0ac ipmsteven 1418373246 -0800 checkout: moving from feature to master a78bcab6232e9382a86436cdfcb2ed0391b1f0ac 98205e9faf10cf33d2ef7c0f66e402540c62613a ipmsteven 1418373270 -0800 commit: add content 98205e9faf10cf33d2ef7c0f66e402540c62613a ed0fdcf366b21b8984fb37ea34106978a2e5c5ba ipmsteven 1418373282 -0800 checkout: moving from master to feature ed0fdcf366b21b8984fb37ea34106978a2e5c5ba 98205e9faf10cf33d2ef7c0f66e402540c62613a ipmsteven 1418373314 -0800 rebase: checkout master 98205e9faf10cf33d2ef7c0f66e402540c62613a 4419b972c0cd1b346ac90332aa7c5cc949589f78 ipmsteven 1418373314 -0800 rebase: add feature 4419b972c0cd1b346ac90332aa7c5cc949589f78 4419b972c0cd1b346ac90332aa7c5cc949589f78 ipmsteven 1418373314 -0800 rebase finished: returning to refs/heads/feature 4419b972c0cd1b346ac90332aa7c5cc949589f78 98205e9faf10cf33d2ef7c0f66e402540c62613a ipmsteven 1418373423 -0800 checkout: moving from feature to master 98205e9faf10cf33d2ef7c0f66e402540c62613a 4419b972c0cd1b346ac90332aa7c5cc949589f78 ipmsteven 1418373435 -0800 checkout: moving from master to feature 4419b972c0cd1b346ac90332aa7c5cc949589f78 ed0fdcf366b21b8984fb37ea34106978a2e5c5ba ipmsteven 1418373492 -0800 reset: moving to ed0fdcf ed0fdcf366b21b8984fb37ea34106978a2e5c5ba 98205e9faf10cf33d2ef7c0f66e402540c62613a ipmsteven 1418373509 -0800 checkout: moving from feature to master ================================================ FILE: levels/rebase/.githug/logs/refs/heads/feature ================================================ 0000000000000000000000000000000000000000 a78bcab6232e9382a86436cdfcb2ed0391b1f0ac ipmsteven 1418373194 -0800 branch: Created from HEAD a78bcab6232e9382a86436cdfcb2ed0391b1f0ac ed0fdcf366b21b8984fb37ea34106978a2e5c5ba ipmsteven 1418373239 -0800 commit: add feature ed0fdcf366b21b8984fb37ea34106978a2e5c5ba 4419b972c0cd1b346ac90332aa7c5cc949589f78 ipmsteven 1418373314 -0800 rebase finished: refs/heads/feature onto 98205e9faf10cf33d2ef7c0f66e402540c62613a 4419b972c0cd1b346ac90332aa7c5cc949589f78 ed0fdcf366b21b8984fb37ea34106978a2e5c5ba ipmsteven 1418373492 -0800 reset: moving to ed0fdcf ================================================ FILE: levels/rebase/.githug/logs/refs/heads/master ================================================ 0000000000000000000000000000000000000000 a78bcab6232e9382a86436cdfcb2ed0391b1f0ac ipmsteven 1418373185 -0800 commit (initial): init commit a78bcab6232e9382a86436cdfcb2ed0391b1f0ac 98205e9faf10cf33d2ef7c0f66e402540c62613a ipmsteven 1418373270 -0800 commit: add content ================================================ FILE: levels/rebase/.githug/objects/98/205e9faf10cf33d2ef7c0f66e402540c62613a ================================================ xQ !@^P2:c-Y[}=x4sΈ`1 LbvHcea mE}R1Yf}]pVS FS1GNkdtT i7`,;g{\\귢{ .+FG8K'픎'.6/#; ================================================ FILE: levels/rebase/.githug/refs/heads/feature ================================================ ed0fdcf366b21b8984fb37ea34106978a2e5c5ba ================================================ FILE: levels/rebase/.githug/refs/heads/master ================================================ 98205e9faf10cf33d2ef7c0f66e402540c62613a ================================================ FILE: levels/rebase/README ================================================ readme ================================================ FILE: levels/rebase.rb ================================================ difficulty 2 description "We are using a git rebase workflow and the feature branch is ready to go into master. Let's rebase the feature branch onto our master branch." setup do init_from_level system "git branch -m master" end solution do return repo.commits('feature').last.id_abbrev != "ed0fdcf" && repo.commits("feature").map(&:message) == ['add feature','add content','init commit'] end hint do puts "You want to research the `git rebase` command" end ================================================ FILE: levels/rebase_onto.rb ================================================ difficulty 2 description "You have created your branch from `wrong_branch` and already made some commits, \ and you realise that you needed to create your branch from `master`. \ Rebase your commits onto `master` branch so that you don't have `wrong_branch` commits." setup do readme_file = "README.md" authors_file = "authors.md" repo.init FileUtils.touch(authors_file) File.open(authors_file, "w") { |f| f << "https://github.com/janis-vitols\n" } repo.add(authors_file) repo.commit_all("Create authors file") system "git branch -m master" repo.git.native :checkout, { "b" => true }, "wrong_branch" File.open(authors_file, "w") { |f| f << "None\n" } repo.add(authors_file) repo.commit_all("Wrong changes") repo.git.native :checkout, { "b" => true }, "readme-update" FileUtils.touch(readme_file) File.open(readme_file, "a") { |f| f << "# SuperApp\n" } repo.add(readme_file) repo.commit_all("Add app name in readme") File.open(readme_file, "a") { |f| f << "## About\n" } repo.add(readme_file) repo.commit_all("Add `About` header in readme") File.open(readme_file, "a") { |f| f << "## Install\n" } repo.add(readme_file) repo.commit_all("Add `Install` header in readme") end solution do repo.commits("readme-update").each { |commit| return false if commit.message == "Wrong changes" } return false unless repo.commits("readme-update").length == 4 return false unless File.readlines("authors.md").include?("https://github.com/janis-vitols\n") true end hint do puts "You want to research the `git rebase` commands `--onto` argument" end ================================================ FILE: levels/remote.rb ================================================ difficulty 2 description "This project has a remote repository. Identify it." setup do repo.init repo.remote_add("my_remote_repo", "https://github.com/Gazler/githug") system "git branch -m master" end solution do "my_remote_repo" == request("What is the name of the remote repository?") end hint do puts "You are looking for a remote. You can run `git` for a list of commands." end ================================================ FILE: levels/remote_add.rb ================================================ difficulty 2 description "Add a remote repository called `origin` with the url https://github.com/githug/githug" setup do repo.init system "git branch -m master" end solution do result = `git remote -v` result.include?("https://github.com/githug/githug") end hint do puts "You can run `git remote --help` for the man pages." end ================================================ FILE: levels/remote_url.rb ================================================ difficulty 2 description "The remote repositories have a url associated to them. Please enter the url of remote_location." setup do repo.init repo.remote_add("my_remote_repo", "https://github.com/Gazler/githug") repo.remote_add("remote_location", "https://github.com/githug/not_a_repo") system "git branch -m master" end solution do !!(request("What is the url of the remote repository?") =~ /https:\/\/github.com\/githug\/not_a_repo\/?/) end hint do puts "You can run `git remote --help` for the man pages." end ================================================ FILE: levels/rename.rb ================================================ difficulty 3 description "We have a file called `oldfile.txt`. We want to rename it to `newfile.txt` and stage this change." setup do repo.init FileUtils.touch("oldfile.txt") repo.add("oldfile.txt") repo.commit_all("Commited oldfile.txt") system "git branch -m master" end solution do repo.status["oldfile.txt"].type == "D" && repo.status["newfile.txt"].type == "A" && repo.status["oldfile.txt"].stage.nil? end hint do puts "Take a look at `git mv`." end ================================================ FILE: levels/rename_commit.rb ================================================ difficulty 3 description "Correct the typo in the message of your first (non-root) commit." setup do repo.init FileUtils.touch "README" repo.add "README" repo.commit_all "Initial commit" system "git branch -m master" FileUtils.touch "file1" repo.add "file1" repo.commit_all "First coommit" FileUtils.touch "file2" repo.add "file2" repo.commit_all "Second commit" end solution do repo.commits.first.parents[0].message == "First commit" end hint do puts "Take a look the `-i` flag of the rebase command." end ================================================ FILE: levels/reorder.rb ================================================ difficulty 4 description "You have committed several times but in the wrong order. Please reorder your commits." setup do repo.init FileUtils.touch "README" repo.add "README" repo.commit_all "Initial Setup" system "git branch -m master" FileUtils.touch "file1" repo.add "file1" repo.commit_all "First commit" FileUtils.touch "file3" repo.add "file3" repo.commit_all "Third commit" FileUtils.touch "file2" repo.add "file2" repo.commit_all "Second commit" end solution do `git log --format="%s"`.split.join("").match /Third.*Second.*First.*Initial/ end hint do puts "Take a look the `-i` flag of the rebase command." end ================================================ FILE: levels/repack.rb ================================================ difficulty 2 description "Optimise how your repository is packaged ensuring that redundant packs are removed." setup do repo.init FileUtils.touch("foo") repo.add("foo") repo.commit_all("Added foo") system "git branch -m master" end solution do result = `git count-objects -v` required = ["count: 0", "prune-packable: 0"]; required.all? { |r| result.include?(r) } end hint do puts "You want to research the `git repack` command." end ================================================ FILE: levels/reset.rb ================================================ difficulty 2 description "There are two files to be committed. The goal was to add each file as a separate commit, however both were added by accident. Unstage the file `to_commit_second.rb` using the reset command (don't commit anything)." setup do repo.init FileUtils.touch("README") repo.add("README") repo.commit_all("Initial commit") FileUtils.touch("to_commit_first.rb") FileUtils.touch("to_commit_second.rb") repo.add(".") system "git branch -m master" end solution do return false unless (repo.status.files["to_commit_second.rb"].nil? || repo.status.files["to_commit_second.rb"].stage.nil?) && File.exists?("to_commit_second.rb") return false if (repo.status.files["to_commit_first.rb"].nil? || repo.status.files["to_commit_first.rb"].stage.nil?) true end hint do puts "You can get some useful information for git status, it will tell you the command you need to run." end ================================================ FILE: levels/reset_soft.rb ================================================ difficulty 2 description "You committed too soon. Now you want to undo the last commit, while keeping the index." setup do repo.init FileUtils.touch("README") repo.add("README") repo.commit_all("Initial commit") FileUtils.touch("newfile.rb") repo.add("newfile.rb") repo.commit_all("Premature commit") system "git branch -m master" end solution do return false unless File.exists?("newfile.rb") && repo.status.files.keys.include?("newfile.rb") return false if repo.status.files["newfile.rb"].untracked || repo.commit_count > 1 true end hint do puts "What are some options you can use with `git reset`?" end ================================================ FILE: levels/restore.rb ================================================ difficulty 4 description "You decided to delete your latest commit by running `git reset --hard HEAD^` (not a smart thing to do). Now you changed your mind and want that commit back. Restore the deleted commit." setup do repo.init FileUtils.touch 'file1' repo.add 'file1' repo.commit_all 'Initial commit' system "git branch -m master" FileUtils.touch 'file2' repo.add 'file2' repo.commit_all 'First commit' FileUtils.touch 'file3' repo.add 'file3' repo.commit_all 'Restore this commit' repo.git.native :reset, { "hard" => true }, 'HEAD^' end solution do return false unless File.exists?('file3') true end hint do puts "The commit is still floating around somewhere. Have you checked out `git reflog`?" end ================================================ FILE: levels/restructure.rb ================================================ difficulty 3 description "You added some files to your repository, but now realize that your project needs to be restructured. Make a new folder named `src` and use Git move all of the .html files into this folder." setup do repo.init FileUtils.touch("about.html") FileUtils.touch("contact.html") FileUtils.touch("index.html") repo.add("about.html") repo.add("contact.html") repo.add("index.html") system "git branch -m master" repo.commit_all("adding web content.") end solution do index = repo.status["index.html"].type == "D" && repo.status["index.html"].stage.nil? && repo.status["src/index.html"].type == "A" about = repo.status["about.html"].type == "D" && repo.status["about.html"].stage.nil? && repo.status["src/about.html"].type == "A" contact = repo.status["contact.html"].type == "D" && repo.status["contact.html"].stage.nil? && repo.status["src/contact.html"].type == "A" index && about && contact end hint do puts "You'll have to use mkdir, and `git mv`." end ================================================ FILE: levels/revert.rb ================================================ difficulty 4 description "You have committed several times but want to undo the middle commit. All commits have been pushed, so you can't change existing history." setup do repo.init FileUtils.touch "file1" repo.add "file1" repo.commit_all "First commit" system "git branch -m master" FileUtils.touch "file3" repo.add "file3" repo.commit_all "Bad commit" FileUtils.touch "file2" repo.add "file2" repo.commit_all "Second commit" end solution do valid = false commit_messages = repo.commits.map(&:message) valid = true if repo.commits.length > 3 && commit_messages.any? { |e| e =~ /(Revert )?"Bad commit"/ } valid end hint do puts "Try the revert command." end ================================================ FILE: levels/rm.rb ================================================ difficulty 2 description "A file has been removed from the working tree, but not from the repository. Identify this file and remove it." setup do repo.init file = File.new("deleteme.rb", "w") file.close system "git branch -m master" repo.add("deleteme.rb") repo.commit_all("Added a temp file") File.delete("deleteme.rb") end solution do repo.status.files["deleteme.rb"].nil? || repo.status.files["deleteme.rb"].stage.nil? end hint do puts ["You may need to use more than one command to complete this.", "You have checked your staging area in a previous level.", "Don't forget to run `git` for a list of commands."] end ================================================ FILE: levels/rm_cached.rb ================================================ difficulty 2 description "A file has accidentally been added to your staging area. Identify and remove it from the staging area. *NOTE* Do not remove the file from the file system, only from git." setup do repo.init FileUtils.touch("deleteme.rb") system "git branch -m master" repo.add(".gitignore") repo.add("deleteme.rb") end solution do (repo.status.files["deleteme.rb"].nil? || repo.status.files["deleteme.rb"].stage.nil?) && File.exists?("deleteme.rb") end hint do puts "You may need to use more than one command to complete this. You have checked your staging area in a previous level. Don't forget to run `git` for a list of commands." end ================================================ FILE: levels/squash.rb ================================================ difficulty 4 description "You have committed several times but would like all those changes to be one commit." setup do repo.init FileUtils.touch(".hidden") repo.add(".hidden") repo.commit_all("Initial Commit") system "git branch -m master" FileUtils.touch("README") repo.add("README") repo.commit_all("Adding README") File.open("README", 'w') { |f| f.write("hey there") } repo.add("README") repo.commit_all("Updating README (squash this commit into Adding README)") File.open("README", 'a') { |f| f.write("\nAdding some more text") } repo.add("README") repo.commit_all("Updating README (squash this commit into Adding README)") File.open("README", 'a') { |f| f.write("\neven more text") } repo.add("README") repo.commit_all("Updating README (squash this commit into Adding README)") end solution do repo.commits.length == 2 end hint do puts "Take a look at the `-i` flag of the rebase command." end ================================================ FILE: levels/stage_lines.rb ================================================ difficulty 4 description "You've made changes within a single file that belong to two different features, but neither of the changes are yet staged. Stage only the changes belonging to the first feature." setup do repo.init File.open("feature.rb", "w") do |file| file.puts("this is the class of my feature") end system "git branch -m master" repo.add("feature.rb") repo.commit_all("Added initial feature file") File.open("feature.rb", "a") do |file| file.puts("This change belongs to the first feature") end File.open("feature.rb", "a") do |file| file.puts("This change belongs to the second feature") end end solution do `git diff --no-ext-diff --no-color --staged` =~ /\+This change belongs to the first feature/ && `git diff --no-ext-diff --no-color` =~ /\+This change belongs to the second feature/ end hint do puts "You might want to try to manipulate the hunks of the diff to choose which lines of the diff get staged. Read about the flags which can be passed to the `add` command; `git --help add`." end ================================================ FILE: levels/stash/.githug/COMMIT_EDITMSG ================================================ Add some lyrics ================================================ FILE: levels/stash/.githug/HEAD ================================================ ref: refs/heads/master ================================================ FILE: levels/stash/.githug/config ================================================ [core] repositoryformatversion = 0 filemode = true bare = false logallrefupdates = true ignorecase = true precomposeunicode = false ================================================ FILE: levels/stash/.githug/description ================================================ Unnamed repository; edit this file 'description' to name the repository. ================================================ FILE: levels/stash/.githug/hooks/applypatch-msg.sample ================================================ #!/bin/sh # # An example hook script to check the commit log message taken by # applypatch from an e-mail message. # # The hook should exit with non-zero status after issuing an # appropriate message if it wants to stop the commit. The hook is # allowed to edit the commit message file. # # To enable this hook, rename this file to "applypatch-msg". . git-sh-setup test -x "$GIT_DIR/hooks/commit-msg" && exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"} : ================================================ FILE: levels/stash/.githug/hooks/commit-msg.sample ================================================ #!/bin/sh # # An example hook script to check the commit log message. # Called by "git commit" with one argument, the name of the file # that has the commit message. The hook should exit with non-zero # status after issuing an appropriate message if it wants to stop the # commit. The hook is allowed to edit the commit message file. # # To enable this hook, rename this file to "commit-msg". # Uncomment the below to add a Signed-off-by line to the message. # Doing this in a hook is a bad idea in general, but the prepare-commit-msg # hook is more suited to it. # # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" # This example catches duplicate Signed-off-by lines. test "" = "$(grep '^Signed-off-by: ' "$1" | sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { echo >&2 Duplicate Signed-off-by lines. exit 1 } ================================================ FILE: levels/stash/.githug/hooks/post-update.sample ================================================ #!/bin/sh # # An example hook script to prepare a packed repository for use over # dumb transports. # # To enable this hook, rename this file to "post-update". exec git update-server-info ================================================ FILE: levels/stash/.githug/hooks/pre-applypatch.sample ================================================ #!/bin/sh # # An example hook script to verify what is about to be committed # by applypatch from an e-mail message. # # The hook should exit with non-zero status after issuing an # appropriate message if it wants to stop the commit. # # To enable this hook, rename this file to "pre-applypatch". . git-sh-setup test -x "$GIT_DIR/hooks/pre-commit" && exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"} : ================================================ FILE: levels/stash/.githug/hooks/pre-commit.sample ================================================ #!/bin/sh # # An example hook script to verify what is about to be committed. # Called by "git commit" with no arguments. The hook should # exit with non-zero status after issuing an appropriate message if # it wants to stop the commit. # # To enable this hook, rename this file to "pre-commit". if git rev-parse --verify HEAD >/dev/null 2>&1 then against=HEAD else # Initial commit: diff against an empty tree object against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 fi # If you want to allow non-ascii filenames set this variable to true. allownonascii=$(git config hooks.allownonascii) # Redirect output to stderr. exec 1>&2 # Cross platform projects tend to avoid non-ascii filenames; prevent # them from being added to the repository. We exploit the fact that the # printable range starts at the space character and ends with tilde. if [ "$allownonascii" != "true" ] && # Note that the use of brackets around a tr range is ok here, (it's # even required, for portability to Solaris 10's /usr/bin/tr), since # the square bracket bytes happen to fall in the designated range. test $(git diff --cached --name-only --diff-filter=A -z $against | LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 then echo "Error: Attempt to add a non-ascii file name." echo echo "This can cause problems if you want to work" echo "with people on other platforms." echo echo "To be portable it is advisable to rename the file ..." echo echo "If you know what you are doing you can disable this" echo "check using:" echo echo " git config hooks.allownonascii true" echo exit 1 fi # If there are whitespace errors, print the offending file names and fail. exec git diff-index --check --cached $against -- ================================================ FILE: levels/stash/.githug/hooks/pre-rebase.sample ================================================ #!/bin/sh # # Copyright (c) 2006, 2008 Junio C Hamano # # The "pre-rebase" hook is run just before "git rebase" starts doing # its job, and can prevent the command from running by exiting with # non-zero status. # # The hook is called with the following parameters: # # $1 -- the upstream the series was forked from. # $2 -- the branch being rebased (or empty when rebasing the current branch). # # This sample shows how to prevent topic branches that are already # merged to 'next' branch from getting rebased, because allowing it # would result in rebasing already published history. publish=next basebranch="$1" if test "$#" = 2 then topic="refs/heads/$2" else topic=`git symbolic-ref HEAD` || exit 0 ;# we do not interrupt rebasing detached HEAD fi case "$topic" in refs/heads/??/*) ;; *) exit 0 ;# we do not interrupt others. ;; esac # Now we are dealing with a topic branch being rebased # on top of master. Is it OK to rebase it? # Does the topic really exist? git show-ref -q "$topic" || { echo >&2 "No such branch $topic" exit 1 } # Is topic fully merged to master? not_in_master=`git rev-list --pretty=oneline ^master "$topic"` if test -z "$not_in_master" then echo >&2 "$topic is fully merged to master; better remove it." exit 1 ;# we could allow it, but there is no point. fi # Is topic ever merged to next? If so you should not be rebasing it. only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` only_next_2=`git rev-list ^master ${publish} | sort` if test "$only_next_1" = "$only_next_2" then not_in_topic=`git rev-list "^$topic" master` if test -z "$not_in_topic" then echo >&2 "$topic is already up-to-date with master" exit 1 ;# we could allow it, but there is no point. else exit 0 fi else not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` /usr/bin/perl -e ' my $topic = $ARGV[0]; my $msg = "* $topic has commits already merged to public branch:\n"; my (%not_in_next) = map { /^([0-9a-f]+) /; ($1 => 1); } split(/\n/, $ARGV[1]); for my $elem (map { /^([0-9a-f]+) (.*)$/; [$1 => $2]; } split(/\n/, $ARGV[2])) { if (!exists $not_in_next{$elem->[0]}) { if ($msg) { print STDERR $msg; undef $msg; } print STDERR " $elem->[1]\n"; } } ' "$topic" "$not_in_next" "$not_in_master" exit 1 fi exit 0 ################################################################ This sample hook safeguards topic branches that have been published from being rewound. The workflow assumed here is: * Once a topic branch forks from "master", "master" is never merged into it again (either directly or indirectly). * Once a topic branch is fully cooked and merged into "master", it is deleted. If you need to build on top of it to correct earlier mistakes, a new topic branch is created by forking at the tip of the "master". This is not strictly necessary, but it makes it easier to keep your history simple. * Whenever you need to test or publish your changes to topic branches, merge them into "next" branch. The script, being an example, hardcodes the publish branch name to be "next", but it is trivial to make it configurable via $GIT_DIR/config mechanism. With this workflow, you would want to know: (1) ... if a topic branch has ever been merged to "next". Young topic branches can have stupid mistakes you would rather clean up before publishing, and things that have not been merged into other branches can be easily rebased without affecting other people. But once it is published, you would not want to rewind it. (2) ... if a topic branch has been fully merged to "master". Then you can delete it. More importantly, you should not build on top of it -- other people may already want to change things related to the topic as patches against your "master", so if you need further changes, it is better to fork the topic (perhaps with the same name) afresh from the tip of "master". Let's look at this example: o---o---o---o---o---o---o---o---o---o "next" / / / / / a---a---b A / / / / / / / / c---c---c---c B / / / / \ / / / / b---b C \ / / / / / \ / ---o---o---o---o---o---o---o---o---o---o---o "master" A, B and C are topic branches. * A has one fix since it was merged up to "next". * B has finished. It has been fully merged up to "master" and "next", and is ready to be deleted. * C has not merged to "next" at all. We would want to allow C to be rebased, refuse A, and encourage B to be deleted. To compute (1): git rev-list ^master ^topic next git rev-list ^master next if these match, topic has not merged in next at all. To compute (2): git rev-list master..topic if this is empty, it is fully merged to "master". ================================================ FILE: levels/stash/.githug/hooks/prepare-commit-msg.sample ================================================ #!/bin/sh # # An example hook script to prepare the commit log message. # Called by "git commit" with the name of the file that has the # commit message, followed by the description of the commit # message's source. The hook's purpose is to edit the commit # message file. If the hook fails with a non-zero status, # the commit is aborted. # # To enable this hook, rename this file to "prepare-commit-msg". # This hook includes three examples. The first comments out the # "Conflicts:" part of a merge commit. # # The second includes the output of "git diff --name-status -r" # into the message, just before the "git status" output. It is # commented because it doesn't cope with --amend or with squashed # commits. # # The third example adds a Signed-off-by line to the message, that can # still be edited. This is rarely a good idea. case "$2,$3" in merge,) /usr/bin/perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;; # ,|template,) # /usr/bin/perl -i.bak -pe ' # print "\n" . `git diff --cached --name-status -r` # if /^#/ && $first++ == 0' "$1" ;; *) ;; esac # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" ================================================ FILE: levels/stash/.githug/hooks/update.sample ================================================ #!/bin/sh # # An example hook script to blocks unannotated tags from entering. # Called by "git receive-pack" with arguments: refname sha1-old sha1-new # # To enable this hook, rename this file to "update". # # Config # ------ # hooks.allowunannotated # This boolean sets whether unannotated tags will be allowed into the # repository. By default they won't be. # hooks.allowdeletetag # This boolean sets whether deleting tags will be allowed in the # repository. By default they won't be. # hooks.allowmodifytag # This boolean sets whether a tag may be modified after creation. By default # it won't be. # hooks.allowdeletebranch # This boolean sets whether deleting branches will be allowed in the # repository. By default they won't be. # hooks.denycreatebranch # This boolean sets whether remotely creating branches will be denied # in the repository. By default this is allowed. # # --- Command line refname="$1" oldrev="$2" newrev="$3" # --- Safety check if [ -z "$GIT_DIR" ]; then echo "Don't run this script from the command line." >&2 echo " (if you want, you could supply GIT_DIR then run" >&2 echo " $0 )" >&2 exit 1 fi if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then echo "Usage: $0 " >&2 exit 1 fi # --- Config allowunannotated=$(git config --bool hooks.allowunannotated) allowdeletebranch=$(git config --bool hooks.allowdeletebranch) denycreatebranch=$(git config --bool hooks.denycreatebranch) allowdeletetag=$(git config --bool hooks.allowdeletetag) allowmodifytag=$(git config --bool hooks.allowmodifytag) # check for no description projectdesc=$(sed -e '1q' "$GIT_DIR/description") case "$projectdesc" in "Unnamed repository"* | "") echo "*** Project description file hasn't been set" >&2 exit 1 ;; esac # --- Check types # if $newrev is 0000...0000, it's a commit to delete a ref. zero="0000000000000000000000000000000000000000" if [ "$newrev" = "$zero" ]; then newrev_type=delete else newrev_type=$(git cat-file -t $newrev) fi case "$refname","$newrev_type" in refs/tags/*,commit) # un-annotated tag short_refname=${refname##refs/tags/} if [ "$allowunannotated" != "true" ]; then echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2 echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 exit 1 fi ;; refs/tags/*,delete) # delete tag if [ "$allowdeletetag" != "true" ]; then echo "*** Deleting a tag is not allowed in this repository" >&2 exit 1 fi ;; refs/tags/*,tag) # annotated tag if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 then echo "*** Tag '$refname' already exists." >&2 echo "*** Modifying a tag is not allowed in this repository." >&2 exit 1 fi ;; refs/heads/*,commit) # branch if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then echo "*** Creating a branch is not allowed in this repository" >&2 exit 1 fi ;; refs/heads/*,delete) # delete branch if [ "$allowdeletebranch" != "true" ]; then echo "*** Deleting a branch is not allowed in this repository" >&2 exit 1 fi ;; refs/remotes/*,commit) # tracking branch ;; refs/remotes/*,delete) # delete tracking branch if [ "$allowdeletebranch" != "true" ]; then echo "*** Deleting a tracking branch is not allowed in this repository" >&2 exit 1 fi ;; *) # Anything else (is there anything else?) echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 exit 1 ;; esac # --- Finished exit 0 ================================================ FILE: levels/stash/.githug/info/exclude ================================================ # git ls-files --others --exclude-from=.git/info/exclude # Lines that start with '#' are comments. # For a project mostly in C, the following would be a good set of # exclude patterns (uncomment them if you want to use them): # *.[oa] # *~ ================================================ FILE: levels/stash/.githug/logs/HEAD ================================================ 0000000000000000000000000000000000000000 02060592b31c9e12ffe1b282addf9537c5ef8e1f Anton Vasin 1354518787 +0400 commit (initial): Add some lyrics ================================================ FILE: levels/stash/.githug/logs/refs/heads/master ================================================ 0000000000000000000000000000000000000000 02060592b31c9e12ffe1b282addf9537c5ef8e1f Anton Vasin 1354518787 +0400 commit (initial): Add some lyrics ================================================ FILE: levels/stash/.githug/refs/heads/master ================================================ 02060592b31c9e12ffe1b282addf9537c5ef8e1f ================================================ FILE: levels/stash/lyrics.txt ================================================ Down in Louisiana in that sunny clime, They play a class of music that is super fine, And it makes no difference if its rain or shine, You can hear that that jazz band music playing all the time. It sounds so peculiar cause the music's queer. How its sweet vibration seem to fill the air. Then to you the whole world seems to be in rhyme. You want nothing else but blues-band music all the time. Ev'ry one that's nigh Never seems to sigh, Hear them loudly cry: Hey! ================================================ FILE: levels/stash.rb ================================================ difficulty 2 description "You've made some changes and want to work on them later. You should save them, but don't commit them." setup do init_from_level system "git branch -m master" end solution do return false if `git stash list` !~ /stash@\{0\}/ return false if repo.status.changed.to_a.flatten.include? "lyrics.txt" true end hint do puts "It's like stashing. Try finding an appropriate git command." end ================================================ FILE: levels/status.rb ================================================ difficulty 1 description "Among the files in this repository, which of them is untracked?" setup do repo.init %w{config.rb README setup.rb deploy.rb Guardfile}.each do |file| FileUtils.touch(file) system "git branch -m master" repo.add(file) end FileUtils.touch("database.yml") end solution do name = request("What is the full file name of the untracked file?") if name != "database.yml" return false end true end hint do puts "You are looking for a command to identify the status of the repository." end ================================================ FILE: levels/submodule.rb ================================================ difficulty 2 description "You want to include the files from the following repo: `https://github.com/jackmaney/githug-include-me` into the folder `./githug-include-me`. Do this without manually cloning the repo or copying the files from the repo into this repo." setup do repo.init system "git branch -m master" end solution do return false if not File.directory?("./githug-include-me") return false if not File.exist?("./githug-include-me/README.md") return false if not File.exist?("./githug-include-me/.git") return false if File.directory?("./githug-include-me/.git") return false if not File.exist?(".gitmodules") return true end hint do puts "Take a look at `git submodule`." end ================================================ FILE: levels/tag.rb ================================================ difficulty 2 description "We have a git repo and we want to tag the current commit with `new_tag`." setup do repo.init FileUtils.touch("somefile.txt") repo.add("somefile.txt") repo.commit_all("Added some file to the repo") system "git branch -m master" end solution do repo.tags.first.name == "new_tag" end hint do puts "Take a look at `git tag`." end ================================================ FILE: lib/githug/cli.rb ================================================ require 'thor' require 'githug' module Githug class CLI < Thor default_task :play desc :play, "Initialize the game" def play UI.word_box("Githug") make_directory! Game.new.play_level end desc :test, "Test a level from a file path" method_option :errors, :type => :boolean, :default => false def test(path) UI.word_box("Githug") make_directory! level = Level.load_from_file(path) Game.new.test_level(level, options[:errors]) end desc :hint, "Get a hint for the current level" def hint if level = load_level level.show_hint end end desc :reset, "Reset the current level or select specific level" long_desc <<-LONGDESC `githug reset` will reset the current level. You can optionally specify a LEVEL parameter which will reset the game to a specific level. For example: > $ githug reset merge_squash # or $ githug reset 47 Will reset githug to level '#47: merge_squash' LONGDESC def reset(path = nil) level = load_level(path) UI.word_box("Githug") if level UI.puts("resetting level") level.setup_level level.full_description else UI.error("Level does not exist") end end desc :levels, "List all of the levels" def levels list_with_numbers = Level.list.each_with_index.map do |name, index| "##{index + 1}: #{name}" end UI.puts(list_with_numbers) end no_tasks do def load_level(path = nil) return load_level_from_profile unless path return load_level_from_number(path.to_i) if path.to_i.to_s == path return load_level_from_name(path) if Level.list.include?(path) Level.load_from_file(path) end def load_level_from_number(number) level_name = number >= 1 ? Level.list[number - 1] : nil return load_level_from_name(level_name) end def load_level_from_name(name) profile = Profile.load profile.set_level(name) Level.load(name) end def load_level_from_profile profile = Profile.load Level.load(profile.level) end def make_directory! return if File.basename(Dir.pwd) == "git_hug" check_githug_directory! prompt_githug_directory! make_githug_directory! end def check_githug_directory! if File.exists?("./git_hug") UI.puts "Please change into the git_hug directory" exit end end def prompt_githug_directory! unless UI.ask("No githug directory found, do you wish to create one?") UI.puts("Exiting") exit end end def make_githug_directory! Dir.mkdir("./git_hug") Dir.chdir("git_hug") end end end end ================================================ FILE: lib/githug/extensions/grit/ruby1.9.rb ================================================ class String if self.method_defined?(:ord) def getord(offset); self[offset].ord; end else alias :getord :[] end unless self.method_defined?(:b) if self.method_defined?(:force_encoding) def b; self.dup.force_encoding(Encoding::ASCII_8BIT); end else def b; self.dup; end end end end if Object.const_defined?(:PACK_IDX_SIGNATURE) Object.send(:remove_const, :PACK_IDX_SIGNATURE) end PACK_IDX_SIGNATURE = "\377tOc".b ================================================ FILE: lib/githug/game.rb ================================================ module Githug class Game attr_accessor :profile def initialize @profile = Profile.load end def play_level solve = true if profile.level.nil? UI.puts("Welcome to Githug!") solve = false level_bump else level = Level.load(profile.level) if solve && level if level.solve UI.success "Congratulations, you have solved the level!" level_bump else UI.error "Sorry, this solution is not quite right!" profile.current_attempts += 1 profile.save if (profile.current_attempts > 2 && profile.current_attempts % 3 == 0) UI.error "Don't forget you can type `githug hint` for a hint and `githug reset` to reset the current level." end UI.puts level.full_description end end end end def test_level(level, errors = nil) UI.puts level.full_description method = :solve method = :test if errors if level.send(method) UI.success "Valid solution" else UI.error "Invalid solution" end end def level_bump profile.level_bump if level = Level.load(profile.level) UI.puts(level.full_description) level.setup_level end end end end ================================================ FILE: lib/githug/level.rb ================================================ module Githug class Level include UI LEVELS = [nil, "init", "config", "add", "commit", "clone", "clone_to_folder", "ignore", "include", "status", "number_of_files_committed", "rm", "rm_cached", "stash", "rename", "restructure", "log", "tag", "push_tags", "commit_amend", "commit_in_future", "reset", "reset_soft", "checkout_file", "remote", "remote_url", "pull", "remote_add", "push", "diff", "blame", "branch", "checkout", "checkout_tag", "checkout_tag_over_branch", "branch_at", "delete_branch", "push_branch", "merge", "fetch", "rebase", "rebase_onto", "repack", "cherry-pick", "grep", "rename_commit", "squash", "merge_squash", "reorder", "bisect", "stage_lines", "find_old_branch", "revert", "restore", "conflict", "submodule","contribute"] attr_accessor :level_no, :level_path, :level_name class << self def load(level_name) path = "#{File.dirname(__FILE__)}/../../levels/#{level_name}.rb" setup(path) end def load_from_file(path) setup(path) end def list return LEVELS - [nil] end def setup(path) level_path = path.chomp(File.extname(path)) level = self.new return false unless File.exists?(path) level.instance_eval(File.read(path)) level.level_name = File.basename(path, File.extname(path)) level.level_no = LEVELS.index(level.level_name) || 1 level.level_path = level_path level end end def init_from_level FileUtils.cp_r("#{level_path}/.", ".") FileUtils.mv(".githug", ".git") end def difficulty(num) @difficulty = num end def description(description) @description = description end def solution(&block) singleton = class << self; self end singleton.send :define_method, :_solution, &block end def setup(&block) @setup = block end def hint(&hint) @hint = hint end def hints(hints) @hints = hints end def full_description UI.puts UI.puts "Name: #{level_name}" UI.puts "Level: #{level_no}" UI.puts "Difficulty: #{"*"*@difficulty}" UI.puts UI.puts @description UI.puts end def setup_level repo.reset @setup.call if @setup end def repo(location = "") @repo ||= Repository.new(location) end def solve _solution rescue false end def test _solution end def show_hint UI.word_box("Githug") profile = Profile.load current_hint_index = profile.current_hint_index if @hints puts @hints[current_hint_index] if current_hint_index < @hints.size - 1 profile.current_hint_index += 1 profile.save else profile.current_hint_index = 0 profile.save end elsif @hint @hint.call else UI.puts("No hints available for this level.") end end end end ================================================ FILE: lib/githug/profile.rb ================================================ require 'yaml' module Githug class Profile PROFILE_FILE = ".profile.yml" attr_accessor :settings class << self def load self.new(settings) end private def settings return defaults unless File.exists?(PROFILE_FILE) defaults.merge(YAML::load(File.open(PROFILE_FILE))) end def defaults { :level => nil, :current_attempts => 0, :current_hint_index => 0, :current_levels => [], :completed_levels => [] } end end def method_missing(method, *args, &block) if method.to_s.end_with?("=") method = method.to_s.chop.to_sym return settings[method] = args[0] if settings.include?(method) end return(settings[method]) if settings.include?(method) super end def initialize(settings) @settings = settings end def save File.open(PROFILE_FILE, 'w') do |out| YAML.dump(settings, out) end end def set_level(name) settings[:level] = name reset! save end def level_bump settings[:completed_levels] << level settings[:current_levels] = levels set_level(next_level) end private def levels Level::LEVELS end def next_level (levels - settings[:completed_levels]).first || levels.last end def reset! settings[:current_attempts] = 0 settings[:current_hint_index] = 0 end end end ================================================ FILE: lib/githug/repository.rb ================================================ module Githug class Repository attr_accessor :grit def initialize(location = ".") @grit = Grit::Repo.new(location) rescue Grit::InvalidGitRepositoryError @grit = nil end def reset dont_delete = ["..", ".", ".profile.yml"] if File.basename(Dir.pwd) == "git_hug" Dir.entries(Dir.pwd).each do |file| FileUtils.rm_rf(file) unless dont_delete.include?(file) end end create_gitignore end def create_gitignore Dir.chdir("git_hug") if File.exists?("./git_hug") File.open(".gitignore", "w") do |file| file.puts(".profile.yml") file.puts(".gitignore") end end def valid? !@grit.nil? end # Initialize a Git repo. If the repo already exists, do nothing. def init(location = ".") @grit = Grit::Repo.init(location) end def method_missing(method, *args, &block) if @grit && @grit.respond_to?(method) return @grit.send(method, *args, &block) end super end end end ================================================ FILE: lib/githug/ui.rb ================================================ module Githug module UI class << self attr_accessor :out_stream, :in_stream @out_stream = STDOUT @in_stream = STDIN def puts(string = "") out_stream.puts(string) end def print(string) out_stream.print(string) end def gets in_stream.gets end def word_box(string,width=80,char='*') puts char*width puts "#{char}#{string.center(width-2)}#{char}" puts char*width end def request(msg) print("#{msg} ") gets.chomp end def ask(msg) request("#{msg} [yn] ") == 'y' end def colorize(text, color_code) return puts text if ENV['OS'] && ENV['OS'].downcase.include?("windows") puts "#{color_code}#{text}\033[0m" end def error(text) colorize(text, "\033[31m") end def success(text) colorize(text, "\033[32m") end end def method_missing(method, *args, &block) return UI.send(method, *args) if UI.methods(false).include?(method.to_s) || UI.methods(false).include?(method) super end end end ================================================ FILE: lib/githug/version.rb ================================================ module Githug VERSION = "0.5.1" end ================================================ FILE: lib/githug.rb ================================================ require 'grit' require "githug/extensions/grit/ruby1.9" require "githug/version" require 'githug/ui' require 'githug/game' require 'githug/profile' require 'githug/level' require 'githug/repository' Githug::UI.in_stream = STDIN Githug::UI.out_stream = STDOUT STDIN.sync = true ================================================ FILE: spec/githug/cli_spec.rb ================================================ require 'spec_helper' require 'githug/cli' describe Githug::CLI do before(:each) do game = mock.as_null_object Githug::Game.stub(:new).and_return(game) end it "prints the logo" do Githug::UI.should_receive(:word_box).with("Githug") subject.stub(:make_directory!) subject.play end it "creates a directory if one does not exist" do Githug::UI.stub(:ask).and_return(true) Dir.should_receive(:mkdir).with("./git_hug") Dir.should_receive(:chdir).with("git_hug") subject.make_directory! end it "does not create a directory if you are in the game directory" do Dir.stub(:pwd).and_return("/home/git_hug") Githug::UI.should_not_receive(:ask) subject.make_directory! end it "exits if the user selects no" do Githug::UI.stub(:ask).and_return(false) lambda {subject.prompt_githug_directory!}.should raise_error(SystemExit) end it "prompts to change into the directory if it exists" do File.stub(:exists?).and_return(true) Githug::UI.should_receive(:puts).with("Please change into the git_hug directory") lambda {subject.check_githug_directory!}.should raise_error(SystemExit) end describe "#test" do it "performs a test run of the level" do level = mock game = mock subject.stub(:make_directory!) Githug::Level.should_receive(:load_from_file).with("/foo/bar/test/level.rb").and_return(level) Githug::Game.stub(:new).and_return(game) game.should_receive(:test_level).with(level, anything) subject.test("/foo/bar/test/level.rb") end end describe "level methods" do let(:level) { mock } let(:profile) { mock } before(:each) do profile.stub(:level).and_return(1) Githug::Profile.stub(:load).and_return(profile) Githug::Level.stub(:load).and_return(level) Githug::Level.stub(:load_from_file).with("/foo/bar/level.rb").and_return(level) end it "calls the hint method on the level" do level.should_receive(:show_hint) subject.hint end describe "#reset" do it "resets the current level" do level.should_receive(:setup_level) level.should_receive(:full_description) Githug::UI.should_receive(:word_box).with("Githug") Githug::UI.should_receive(:puts).with("resetting level") subject.reset end it "does not reset if the level cannot be loaded" do Githug::Level.stub(:load).and_return(false) level.should_not_receive(:setup_level) level.should_not_receive(:full_description) Githug::UI.should_receive(:error).with("Level does not exist") subject.reset end it "resets the level with a level name" do level.should_receive(:setup_level) level.should_receive(:full_description) profile = mock Githug::Profile.stub(:load).and_return(profile) profile.should_receive(:set_level).with("add") Githug::Level.should_receive(:load).with("add").and_return(level) Githug::UI.should_receive(:word_box).with("Githug") Githug::UI.should_receive(:puts).with("resetting level") subject.reset("add") end it "resets the level with a level number" do level.should_receive(:setup_level) level.should_receive(:full_description) profile = mock Githug::Profile.stub(:load).and_return(profile) profile.should_receive(:set_level).with("rename_commit") Githug::Level.should_receive(:load).with("rename_commit").and_return(level) Githug::UI.should_receive(:word_box).with("Githug") Githug::UI.should_receive(:puts).with("resetting level") subject.reset("45") end it "resets the level with a path" do level.should_receive(:setup_level) level.should_receive(:full_description) Githug::UI.should_receive(:word_box).with("Githug") Githug::UI.should_receive(:puts).with("resetting level") subject.reset("/foo/bar/level.rb") end end end describe "#levels" do it "prints the levels and their numbers" do Githug::Level.stub(:list).and_return(["commit", "add"]) Githug::UI.should_receive(:puts).with(["#1: commit", "#2: add"]) subject.levels end end end ================================================ FILE: spec/githug/game_spec.rb ================================================ require 'spec_helper' describe Githug::Game do let(:profile) { mock(:level => 1, :current_attempts => 0).as_null_object } let(:game) { Githug::Game.new } let(:level) { mock(:full_description => nil, :setup_level => nil) } before(:each) do Githug::Profile.stub(:new).and_return(profile) profile.stub(:save) profile.stub(:level_bump) Githug::UI.stub(:puts) Githug::Level.stub(:load).and_return(level) end it "has a profile" do game.profile.should eql(profile) end it "shows a description if the level is 0" do level.should_not_receive(:solve) profile.stub(:level).and_return(nil) profile.should_receive(:level_bump) Githug::UI.should_receive(:puts).with("Welcome to Githug!") game.play_level end describe "play_level" do it "outputs congratulations if the level is solved" do level.stub(:solve).and_return(true) profile.should_receive(:level_bump) Githug::UI.should_receive(:success).with("Congratulations, you have solved the level!") game.play_level end it "outputs a message if the solution is not right" do level.stub(:solve).and_return(false) Githug::UI.should_receive(:error).with("Sorry, this solution is not quite right!") game.play_level end it "increments the number of failed attempts" do level.stub(:solve).and_return(false) profile.should_receive(:current_attempts=).with(1) profile.should_receive(:save) game.play_level end it "prompts for a hint if the user has failed 3 times." do profile.stub(:current_attempts).and_return(3) level.stub(:solve).and_return(false) Githug::UI.should_receive(:error).with("Sorry, this solution is not quite right!") Githug::UI.should_receive(:error).with("Don't forget you can type `githug hint` for a hint and `githug reset` to reset the current level.") game.play_level end end describe "test_level" do it "outputs Valid solution if the solution is valid" do level.stub(:solve).and_return(true) Githug::UI.should_receive(:success).with("Valid solution") game.test_level(level) end it "outputs Invalid solution if the solution is invalid" do level.stub(:solve).and_return(false) Githug::UI.should_receive(:error).with("Invalid solution") game.test_level(level) end it "calls test when errors is true" do level.should_receive(:test) game.test_level(level, true) end end it "outputs the description of the next level" do level.should_receive(:full_description) profile.stub(:level=) game.level_bump end it "calls setup_level for the next level" do level.should_receive(:setup_level) profile.stub(:level=) game.level_bump end end ================================================ FILE: spec/githug/level_spec.rb ================================================ require 'spec_helper' require 'grit' describe Githug::Level do let(:subject) { Githug::Level.load_from_file(File.expand_path("spec/support/files/test_level.rb")) } let(:repo) { mock(:reset) } before(:each) do Githug::Repository.stub(:new).and_return(repo) Githug::UI.stub(:puts) Githug::UI.stub(:print) end it "should mixin UI" do Githug::Level.ancestors.should include(Githug::UI) end describe ".load" do it "loads the level" do File.stub(:dirname).and_return("") Githug::Level.should_receive(:setup).with("/../../levels/init.rb") Githug::Level.load("init") end it "returns false if the level does not exist" do File.stub(:exists?).and_return(false) Githug::Level.load(1).should eql(false) end end describe ".list" do it "lists the levels without nil" do Githug::Level.list.should eql(Githug::Level::LEVELS - [nil]) end end describe ".load_from_file" do it "loads the level" do subject.instance_variable_get("@difficulty").should eql(1) subject.instance_variable_get("@description").should eql("A test description") end it "return false if the level does not exist" do File.stub(:exists?).and_return(false) Githug::Level.load_from_file("/foo/bar/test/level.rb").should eql(false) end end describe ".setup" do it "returns false if the level does not exist" do File.stub(:exists?).and_return(false) Githug::Level.setup("/foo/bar/test/level.rb").should eql(false) end end describe "#solve" do it "returns false if the level requirements have not been met" do subject.solve.should eql(false) end it "returns true if the level requirements have been met" do Grit::Repo.stub(:new).and_return(true) subject.solve.should eql(true) end end describe "#test" do it "calls solve" do subject.should_receive(:_solution) subject.test end end describe "#full_description" do it "displays a full description" do Githug::UI.stub(:puts) Githug::UI.should_receive(:puts).with("Level: 1") Githug::UI.should_receive(:puts).with("Difficulty: *") Githug::UI.should_receive(:puts).with("A test description") subject.full_description end end describe "#setup" do it "calls setup" do repo.should_receive(:reset) subject.setup_level.should eql("test") end it "does not call the setup if none exists" do subject.instance_variable_set("@setup", nil) lambda {subject.setup_level}.should_not raise_error(NoMethodError) end end describe "#repo" do it "initializes a repository when repo is called" do subject.repo.should equal(repo) Githug::Repository.should_not_receive(:new) subject.repo.should equal(repo) end end describe "#hint" do let(:profile) { mock.as_null_object } before(:each) do profile.stub(:current_hint_index).and_return(0,0,1,0) Githug::Profile.stub(:load).and_return(profile) end it "returns sequential hint if there are multiple" do subject.should_receive(:puts).ordered.with("this is hint 1") subject.show_hint subject.should_receive(:puts).ordered.with("this is hint 2") subject.show_hint subject.should_receive(:puts).ordered.with("this is hint 1") subject.show_hint end it "displays a hint if there are not multiple" do subject.instance_variable_set("@hints", nil) subject.should_receive(:puts).with("this is a hint") subject.show_hint end it "does not call the hint if none exist" do subject.instance_variable_set("@hint", nil) lambda {subject.show_hint}.should_not raise_error(NoMethodError) end end describe "#init_from_level" do it "copies the files from the level folder" do FileUtils.should_receive(:cp_r).with("#{subject.level_path}/.", ".") FileUtils.should_receive(:mv).with(".githug", ".git") subject.init_from_level end end end ================================================ FILE: spec/githug/profile_spec.rb ================================================ require 'spec_helper' describe Githug::Profile do describe ".load" do it "loads the profile" do settings = {:level => 1, :current_attempts => 0, :current_hint_index => 0, :current_levels => [], :completed_levels => []} File.should_receive(:exists?).with(Githug::Profile::PROFILE_FILE).and_return(true) File.should_receive(:open).with(Githug::Profile::PROFILE_FILE).and_return("settings") YAML.should_receive(:load).with("settings").and_return(settings) Githug::Profile.should_receive(:new).with(settings) Githug::Profile.load end it "loads the defaults if the file does not exist" do defaults = {:level => nil, :current_attempts => 0, :current_hint_index => 0, :current_levels => [], :completed_levels => []} File.should_receive(:exists?).with(Githug::Profile::PROFILE_FILE).and_return(false) Githug::Profile.should_receive(:new).with(defaults) Githug::Profile.load end end it "allows method access to getters and setters" do profile = Githug::Profile.load profile.level.should eql(nil) profile.level = 1 profile.level.should eql(1) end describe ".save" do it "saves the file" do profile = Githug::Profile.load File.should_receive(:open).with(Githug::Profile::PROFILE_FILE, "w") profile.save end end describe "level methods" do let(:profile) { Githug::Profile.load } before(:each) do profile.stub(:save) @levels = Githug::Level::LEVELS Githug::Level::LEVELS = ["init", "add", "rm", "rm_cached", "diff"] profile.level = "init" end after(:each) do Githug::Level::LEVELS = @levels end describe "#level_bump" do it "bumps the level" do profile.should_receive(:set_level).with("add") profile.level_bump end it "resets the current_attempts" do profile.current_attempts = 1 profile.level_bump profile.current_attempts.should eql(0) end it "sets the level to the first incomplete level" do profile.level = "rm_cached" profile.completed_levels = ["init", "add"] profile.level_bump profile.level.should eql("rm") end end describe "#set_level" do it "sets the level" do profile.should_receive(:save) profile.should_receive(:reset!) profile.set_level("rm") profile.settings[:level].should eql("rm") end end end end ================================================ FILE: spec/githug/repository_spec.rb ================================================ require 'spec_helper' describe Githug::Repository do let(:grit) { mock } before(:each) do Grit::Repo.stub(:new).and_return(grit) subject.stub(:create_gitignore) end describe "#initialize" do it "calls grit on initialize" do Grit::Repo.should_receive(:new).with(".").and_return(grit) repo = Githug::Repository.new repo.grit.should equal(grit) end it "contains a nil grit if the repo is invalid" do Grit::Repo.should_receive(:new).and_raise(Grit::InvalidGitRepositoryError) repo = Githug::Repository.new repo.grit.should equal(nil) end it "initializes with a location" do Grit::Repo.should_receive(:new).with("test").and_return(grit) repo = Githug::Repository.new("test") end end describe "#reset" do before(:each) do FileUtils.stub(:rm_rf) end it "does nothing if the current directory isn't git_hug" do Dir.stub(:pwd).and_return("/tmp/foo") FileUtils.should_not_receive(:rm_rf) subject.reset end it "removes all the files except .gitignore and .profile.yml" do Dir.stub(:pwd).and_return("/tmp/git_hug") Dir.stub(:entries).and_return([".profile.yml", ".gitignore", "..", ".", "README", ".git"]) FileUtils.should_receive(:rm_rf).with("README") FileUtils.should_receive(:rm_rf).with(".git") subject.reset end end describe "#create_gitignore" do it "creates a gitignore" do subject.unstub(:create_gitignore) File.stub(:exists?).and_return(true) Dir.should_receive(:chdir).with("git_hug") File.should_receive(:open).with(".gitignore", "w") subject.create_gitignore end end describe "#valid?" do it "is valid if grit exists" do subject.should be_valid end it "is not valid if grit does not exist" do subject.instance_variable_set("@grit", nil) subject.should_not be_valid end end describe "#init" do it "does not add and commit gitignore if prompted" do Grit::Repo.should_receive(:init).with(".") subject.init end end describe "#method_missing" do it "deletegates to grit if the method exists" do grit.should_receive(:respond_to?).with(:valid_method).and_return(true) grit.should_receive(:valid_method) subject.valid_method end it "should not deletegate to grit if the method does not exist" do grit.should_receive(:respond_to?).with(:invalid_method).and_return(false) lambda { subject.invalid_method }.should raise_error(NoMethodError) end end end ================================================ FILE: spec/githug/ui_spec.rb ================================================ require 'spec_helper' require 'spec_helper' describe Githug::UI do let(:ui_out) { StringIO.new } let(:ui_in) { StringIO.new } before(:each) do subject.out_stream = ui_out subject.in_stream = ui_in end it "puts to the stream" do subject.puts("hello") ui_out.string.should eql("hello\n") end it "prints an empty line with no arguments" do subject.puts ui_out.string.should eql("\n") end it "prints without a new line" do subject.print("hello") ui_out.string.should eql("hello") end it "fetches gets from input stream" do ui_in.puts "bar" ui_in.rewind subject.gets.should == "bar\n" end it "makes a wordbox" do word_box = <<-eof ******************************************************************************** * Githug * ******************************************************************************** eof subject.word_box("Githug") ui_out.string.should eql(word_box) end it "prints a correct wordbox for uneven msg length" do subject.word_box("odd",80) printed = ui_out.string.lines first_size = printed.first.chomp.length printed.map{ |line| line.chomp.length.should eq(first_size) } end it "requests text input" do ui_in.puts "bar" ui_in.rewind subject.request("foo").should == "bar" ui_out.string.should == "foo " end it "asks for yes/no and return true when yes" do subject.should_receive(:request).with('foo? [yn] ').and_return('y') subject.ask("foo?").should be_true end it "asks for yes/no and return false when no" do subject.stub(:request).and_return('n') subject.ask("foo?").should be_false end it "asks for yes/no and return false for any input" do subject.stub(:request).and_return('aklhasdf') subject.ask("foo?").should be_false end describe "Non Windows Platform" do before(:each) do ENV.stub(:[]).with("OS").and_return(nil) end it "prints out a success message in green" do subject.success("success") ui_out.string.should eql("\033[32msuccess\033[0m\n") end it "prints out a error message in red" do subject.error("error") ui_out.string.should eql("\033[31merror\033[0m\n") end end describe "Windows Platform" do before(:each) do ENV.stub(:[]).with("OS").and_return("Windows_NT") end it "prints out a success message in white" do subject.success("success") ui_out.string.should eql("success\n") end it "prints out a error message in white" do subject.error("error") ui_out.string.should eql("error\n") end end end ================================================ FILE: spec/githug_spec.rb ================================================ require 'spec_helper' RSpec::Matchers.define :be_solved do match do |actual| !actual.match("Congratulations, you have solved the level!").nil? end end def skip_level Githug::Profile.load.level_bump `githug reset` end describe "The Game" do before(:all) do @dir = Dir.pwd `rake build` `gem install pkg/githug-#{Githug::VERSION}.gem` FileUtils.rm_rf("/tmp/git_hug") Dir.chdir("/tmp") `echo "y" | githug` Dir.chdir("/tmp/git_hug") end after(:all) do Dir.chdir(@dir) end it "solves the init level" do `git init` `githug`.should be_solved end it "solves the config level" do skip_level #The CI server does not have git config set #full_name = `git config --get user.name`.chomp #email = `git config --get user.email`.chomp #f = IO::popen('githug', 'w') #f.puts(full_name) #f.puts(email) #f.close end it "solves the add level" do `git add README` `githug`.should be_solved end it "solves the commit level" do `git commit -m "test message"` `githug`.should be_solved end it "solves the clone level" do `git clone https://github.com/Gazler/cloneme` `githug`.should be_solved end it "solves the clone_to_folder level" do `git clone https://github.com/Gazler/cloneme my_cloned_repo` `githug`.should be_solved end it "solves the ignore level" do `echo "*.swp" >> .gitignore` `githug`.should be_solved end it "solves the include level" do `echo "*.a\n!lib.a" >> .gitignore` `githug`.should be_solved end it "solves the status level" do `git ls-files --other --exclude-standard | githug`.should be_solved end it "solves the number of files committed level" do `git diff --name-only --cached | wc -l | githug`.should be_solved end it "solves the rm level" do file_name = `git status | grep deleted | cut -d " " -f 5` `git rm #{file_name}` `githug`.should be_solved end it "solves the rm cached level" do file_name = `git status | grep "new file" | cut -d " " -f 5` `git rm --cached #{file_name}` `githug`.should be_solved end it "solves the stash level" do `git stash save` `githug`.should be_solved end it "solves the rename level" do `git mv oldfile.txt newfile.txt` `githug`.should be_solved end it "solves the restructure level" do `mkdir src` `git mv *.html src` `githug`.should be_solved end it "solves the log level" do `git log --pretty=short | grep commit | cut -c 8-14 | githug`.should be_solved end it "solves the tag level" do `git tag new_tag` `githug`.should be_solved end it "solves the push_tags level" do `git push origin master --tags` `githug`.should be_solved end it "solves the commit_amend level" do `git add forgotten_file.rb` `git commit --amend -C HEAD` `githug`.should be_solved end it "solves the commit_in_future level" do authored_date = Time.now + 14 authored_date = authored_date.rfc2822 `git commit -m "Test of future date" --date="#{authored_date}"` `githug`.should be_solved end it "solves the reset level" do `git reset HEAD to_commit_second.rb` `githug`.should be_solved end it "solves the reset_soft level" do `git reset --soft HEAD^` `githug`.should be_solved end it "solves the checkout_file level" do `git checkout -- config.rb` `githug`.should be_solved end it "solves the remove level" do `git remote | githug`.should be_solved end it "solves the remote_url level" do `git remote -v | tail -2 | head -1 | cut -c 17-52 | githug`.should be_solved end it "solves the pull level" do `git pull origin master` `githug`.should be_solved end it "solves the remote_add level" do `git remote add origin https://github.com/githug/githug` `githug`.should be_solved end it "solves the push level" do `git rebase origin/master` `git push origin` `githug`.should be_solved end it "solves the diff level" do `echo "26" | githug`.should be_solved end it "solves the blame level" do `echo "spider man" | githug`.should be_solved end it "solves the branch level" do `git branch test_code` `githug`.should be_solved end it "solves the checkout level" do `git checkout -b my_branch` `githug`.should be_solved end it "solves the checkout_tag level" do `git checkout v1.2` `githug`.should be_solved end it "solves the checkout_tag_over_branch level" do `git checkout tags/v1.2` `githug`.should be_solved end it "solves the branch_at level" do commit = `git log HEAD~1 --pretty=short | head -1 | cut -d " " -f 2` `git branch test_branch #{commit}` `githug`.should be_solved end it "solves the delete_branch level" do `git branch -d delete_me` `githug`.should be_solved end it "solves the push_branch level" do `git push origin test_branch` `githug`.should be_solved end it "should commit the merge level" do `git merge feature` `githug`.should be_solved end it "solves the fetch level" do `git fetch` `githug`.should be_solved end it "solves the rebase level" do `git checkout feature` `git rebase master` `githug`.should be_solved end it "solves the rebase_onto level" do `git checkout readme-update` `git rebase --onto master wrong_branch readme-update` `githug`.should be_solved end it "solves the repack level" do `git repack -d` `githug`.should be_solved end it "solves the cherry-pick level" do commit = `git log new-feature --oneline -n 3 | tail -1 | cut -d " " -f 1` `git cherry-pick #{commit}` `githug`.should be_solved end it "solves the grep level" do `echo "4" | githug`.should be_solved end it "solves the rename_commit level" do skip_level end it "solves the squash level" do skip_level end it "solves the merge squash level" do `git merge --squash long-feature-branch` `git commit -m "Merged Long Feature Branch"` `githug`.should be_solved end it "solves the reorder level" do skip_level end it "solves the bisect level" do `echo "18ed2ac" | githug`.should be_solved end it "solves the stage_lines level" do skip_level end it "solves the find_old_branch level" do `git checkout solve_world_hunger` `githug`.should be_solved end it "solves the revert level" do sleep 1 `git revert HEAD~1 --no-edit` `githug`.should be_solved end it "solves the restore level" do `git reflog | grep "Restore this commit" | awk '{print $1}' | xargs git checkout` `githug`.should be_solved end it "solves the conflict level" do skip_level end it "solves the submodule level" do `git submodule add https://github.com/jackmaney/githug-include-me` `githug`.should be_solved end it "solves the contribute level" do skip_level end end ================================================ FILE: spec/spec_helper.rb ================================================ require './lib/githug.rb' ================================================ FILE: spec/support/files/test_level.rb ================================================ difficulty 1 description "A test description" setup do "test" end solution do Grit::Repo.new("githug/notadir") end hints [ "this is hint 1", "this is hint 2"] hint do puts "this is a hint" end