├── .gitignore ├── .ruby-version ├── Gemfile ├── Gemfile.lock ├── README.md ├── Rakefile ├── abstract.txt ├── diagrams ├── diagrams (Autosaved).graffle └── diagrams.graffle ├── keynote ├── git_extra.key └── git_tutorial.key ├── mindmap └── git_mindmap.graffle ├── pdfs └── git_tutorial.pdf ├── rakelib ├── labs.rake └── run.rake ├── run ├── src ├── UI.js ├── config.rb ├── git_clone.png ├── home.css ├── index.html ├── lab_01.html ├── labs.txt ├── manifest.json ├── reset.css └── screen.css └── templates ├── index.html.erb ├── lab.html.erb ├── lab_index.html.erb └── nav.html.erb /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .zsh_rake_cache 3 | auto 4 | dist 5 | git_tutorial/html 6 | git_tutorial/repos 7 | git_tutorial/work/**/* 8 | samples 9 | served 10 | src/.sass-cache/* 11 | .sass-cache/* 12 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.1.2 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | ruby '3.1.2' 3 | 4 | gem 'RedCloth' 5 | gem 'rake' 6 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | RedCloth (4.2.9) 5 | rake (13.0.6) 6 | 7 | PLATFORMS 8 | ruby 9 | 10 | DEPENDENCIES 11 | RedCloth 12 | rake 13 | 14 | RUBY VERSION 15 | ruby 3.1.2p20 16 | 17 | BUNDLED WITH 18 | 2.1.4 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Git Immersion Labs 2 | 3 | These are the labs for the Git Immersion training, a series of 4 | self-paced exercises that take you through the basics of using git. 5 | 6 | ## Online 7 | 8 | You can find the labs online at 9 | [https://gitimmersion.com](https://gitimmersion.com). 10 | 11 | ## Building the Labs 12 | 13 | The labs are generated from a single source file that describes 14 | each of the labs. The generation is done in two steps. 15 | 16 | Before running the labs, make sure you have the following alias 17 | in your .gitconfig file. The `hist` command is used extensively 18 | throughout the tutorial. 19 | 20 | [alias] 21 | hist = log --pretty=format:'%h %ad | %s%d [%an]' --graph --date=short 22 | 23 | First, the `rake run` command runs through each of the labs and 24 | executes the listed commands and captures the output. The `auto` 25 | directory is used for the automatic running and the output is captured 26 | in the `samples` directory. 27 | 28 | Second, the `rake labs` command generates the HTML labs using the text 29 | from the `src/labs.txt` file and the captured live output from the 30 | `samples` directory. Template files for the main index, the lab 31 | pages, and the navigation divs can be found in the `templates` 32 | directory. 33 | 34 | The HTML output is put into `git_tutorial/html`. Browsing the 35 | `git_tutorial/html/index.html` file will bring up the git tutorial in 36 | your browser. 37 | 38 | ## Publishing the Labs 39 | 40 | To publish the labs on the web-site, run the `rake publish` command. 41 | This will copy the `git_tutorial/html` directory to the `gh-pages` 42 | branch. The `gh-pages` branch is then pushed, which auto-publishes it 43 | from github. 44 | 45 | Manually modifying the files in the `gh-pages` branch is probably the 46 | wrong thing to do. Modify the appropriate template or css file on the 47 | main branch, then run `rake publish`. 48 | 49 | ## Lab Format Directives 50 | 51 | The `labs.txt` file contains all the lab text, formatted as a text 52 | file with additional directives interpreted for both run time 53 | (generating the sample output) and format time (generating the HTML). 54 | 55 | The Format Directives are: 56 | 57 | ### h1. _\_ 58 | 59 | Starts a new lab with the name _\_. Each lab 60 | 61 | Example: 62 | 63 | h1. Using Revert 64 | 65 | ### pre(_\_). 66 | 67 | A section of predefined code, using the HTML class of _\_. The predefined code block runs until a blank line. 69 | 70 | Example: 71 | 72 | pre(instructions). 73 | git log --pretty=oneline --max-count=2 74 | git log --pretty=oneline --since='5 minutes ago' 75 | git log --pretty=oneline --until='5 minutes ago' 76 | 77 | The *instructions* class is used to format command similar to the 78 | execute section, but without executing the commands in the run phase. 79 | 80 | ### p. _\_ 81 | 82 | A paragraph of text. The text for the paragraph will continue on 83 | following lines until a blank line. 84 | 85 | Example: 86 | 87 | p. If you have never used git before, you need to do some setup 88 | first. Run the following commands so that git knows your name and 89 | email. If you have git already setup, you can skip down to the 90 | line ending section. 91 | 92 | ### Execute: 93 | 94 | Execute the following shell command until a blank line is encountered. 95 | Commands are executed as they appear with the following exceptions. 96 | 97 | * +_\_ 98 | 99 | Run this _\_ line silently, do not include it on the lab 100 | output. 101 | 102 | * -_\_ 103 | 104 | Do not run this _\_, but include it in the lab 105 | output. 106 | 107 | * =*\* 108 | 109 | For example, the following will execute the `git status` command and 110 | capture its output in the `status` sample for the lab. The first `git 111 | commit` is ignored at runtime (but will be included in lab output). 112 | The second `git commit` with a commit message will be executed (but 113 | will not appear in the lab output). However, the output of the second 114 | command is captured in a sample. 115 | 116 | Execute: 117 | git status 118 | =status 119 | -git commit 120 | +git commit -m 'Using ARGV' 121 | =commit 122 | 123 | ### File: _\_ 124 | 125 | Format the following lines (until an "EOF" string is encountered) as 126 | the contents of a file name _\_. 127 | 128 | Example: 129 | 130 | File: hello.rb 131 | # This is the hello world program in Ruby. 132 | 133 | puts "Hello, World!" 134 | EOF 135 | 136 | ### Output: 137 | 138 | Format the following line. (until an "EOF" string is encountered) as 139 | the output of commands. 140 | 141 | Output lines starting with = are used to grab the sample files 142 | generated during the run phase. 143 | 144 | Example: 145 | 146 | Output: 147 | git commit 148 | Waiting for Emacs... 149 | [main 569aa96] Using ARGV 150 | 1 files changed, 1 insertions(+), 1 deletions(-) 151 | EOF 152 | 153 | Often sample lines are included in the output. Assuming you have 154 | captured the output of a status command and a commit command, you 155 | might use the following: 156 | 157 | Output: 158 | =status 159 | =commit 160 | EOF 161 | 162 | ### Set: _\_=_\_ 163 | 164 | Evaluate the _\_ and set the _\_ to that 165 | value. Often used to grab dynamic data from the run phase for use in 166 | later commands. 167 | 168 | For example, the following will grab the git hash value for the commit 169 | labeled "First Commit", and store it in _\_. When the `git 170 | checkout` command is executed, it uses the value of _\_ in the 171 | command. 172 | 173 | Set: hash=hash_for("First Commit") 174 | Execute: 175 | git checkout 176 | 177 | ### =_\_ 178 | 179 | Define/use a sample output. 180 | 181 | Sample output are generated during the run phase of building the Git 182 | Immersion labs. They are the output of a single command line in the 183 | Execute section of a lab. 184 | 185 | Example: 186 | 187 | Execute: 188 | git checkout main 189 | =checkout 190 | git status 191 | =status 192 | 193 | The two sample lines above capture the output from the checkout and 194 | status git commands respectively. The sample output is saved (in the 195 | `samples` directory) until the HTML generation phase is performed. 196 | 197 | During HTML generation, the sample lines may be "played back" by 198 | including them in the Output section of a lab. 199 | 200 | Example: 201 | 202 | Output: 203 | =checkout 204 | =status 205 | EOF 206 | 207 | Sample names must be unique within a single lab, but do not have to be 208 | unique across the entire project. 209 | 210 | # License 211 | 212 | ![CC by-nc-sa](http://i.creativecommons.org/l/by-nc-sa/3.0/88x31.png) 213 | 214 | GitImmersion is released under a 215 | [Creative Commons, Attribution-NonCommercial-ShareAlike, Version 3.0](http://creativecommons.org/licenses/by-nc-sa/3.0/) 216 | License. 217 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby -wKU 2 | 3 | require 'rake/clean' 4 | 5 | SAMPLES_DIR = Dir.pwd + "/samples" 6 | SAMPLE_TAG = SAMPLES_DIR + "/buildtag" 7 | 8 | REPOS_DIR = Dir.pwd + "/git_tutorial/repos" 9 | 10 | CLOBBER.include("samples", "auto", "git_tutorial/repos") 11 | 12 | desc "Clean the samples directory" 13 | task :clean_samples do 14 | rm_r SAMPLES_DIR rescue nil 15 | end 16 | 17 | task :default => :labs 18 | 19 | task :rebuild => [:clobber, :run, :labs] 20 | 21 | task :see => [:rebuild, :view] 22 | 23 | task :not_dirty do 24 | fail "Directory not clean" if /nothing to commit/ !~ `git status` 25 | end 26 | 27 | desc "Publish the Git Immersion web site." 28 | task :publish => [:not_dirty, :build, :labs] do 29 | sh 'git checkout main' 30 | head = `git log --pretty="%h" -n1`.strip 31 | sh 'git checkout gh-pages' 32 | cp FileList['git_tutorial/html/*'], '.' 33 | sh 'git add .' 34 | sh "git commit -m 'Updated docs to #{head}'" 35 | sh 'git push' 36 | sh 'git checkout main' 37 | end 38 | 39 | directory "dist" 40 | 41 | file "dist/git_tutorial.zip" => [:build, :labs, "dist"] do 42 | sh 'zip -r dist/git_tutorial.zip git_tutorial' 43 | end 44 | 45 | desc "Create the zipped tutorial" 46 | task :package => [:not_dirty, "dist/git_tutorial.zip"] 47 | 48 | desc "Create the zipped tutorial, but rebuild first" 49 | task :repackage => [:clobber, :package] 50 | 51 | desc "Upload the zipped tutorial to the download site." 52 | task :upload => [:not_dirty, "dist/git_tutorial.zip"] do 53 | sh 'scp dist/git_tutorial.zip linode:htdocs/download/git_tutorial.zip' 54 | end 55 | -------------------------------------------------------------------------------- /abstract.txt: -------------------------------------------------------------------------------- 1 | Git Immersion 2 | ------------- 3 | 4 | Short Description 5 | ----------------- 6 | 7 | Git is a wonderful distributed source control tool with a reputation for being hard to learn. This workshop will sidestep the hard to learn reputation by explaining git in a an easy to learn, bottom-up approach; and then reinforcing that lesson by immersing the attendee into a number of practical hands-on applications of git. This workshop is appropriate for a complete newcomer to git, or for those with some experience who would like a deeper understanding. 8 | 9 | Long Description 10 | ---------------- 11 | 12 | Few tools have changed the way I work as much as the git source control system. Its distributed nature and lightweight branching and merging have made it possible for me to massage my code base in ways I couldn't have even imagined before using git. 13 | 14 | However, git has a reputation for being hard to learn. Because of its rather different approach to source control issues, many of the techniques we have learned in other source control systems do not translate cleanly when using git. 15 | 16 | In this workshop we take two approaches to dealing with the whole "fear of git" issue. First, we explain git from first principles by using an easy to understand, step by step model that leaves nothing to fear. Second, we actually use git in more or less real situations, and become familiar with the tool by using it. 17 | 18 | So, whether you are a complete newcomer to git, or have been using it a while but would like a deeper understanding, this workshop is for you. 19 | 20 | Attendees of this workshop should bring a wifi-enabled laptop with git and ruby pre-installed and ready to go. 21 | 22 | For reference how to install the Ruby-Interpreter on your Operating System visit: 23 | https://www.ruby-lang.org/en/downloads/ 24 | 25 | You will also need to install the RubyGems package manager: 26 | https://rubygems.org/ 27 | -------------------------------------------------------------------------------- /diagrams/diagrams (Autosaved).graffle: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ActiveLayerIndex 6 | 0 7 | ApplicationVersion 8 | 9 | com.omnigroup.OmniGraffle 10 | 138.17.0.133677 11 | 12 | AutoAdjust 13 | 14 | AutosavedUrl 15 | file://localhost/Users/jim/Documents/Presentations/git_immersion/images/diagrams.graffle 16 | BackgroundGraphic 17 | 18 | Bounds 19 | {{0, 0}, {576, 733}} 20 | Class 21 | SolidGraphic 22 | ID 23 | 2 24 | Style 25 | 26 | fill 27 | 28 | Color 29 | 30 | b 31 | 0.909712 32 | g 33 | 0.985809 34 | r 35 | 1 36 | 37 | 38 | shadow 39 | 40 | Draws 41 | NO 42 | 43 | stroke 44 | 45 | Draws 46 | NO 47 | 48 | 49 | 50 | CanvasOrigin 51 | {0, 0} 52 | ColumnAlign 53 | 1 54 | ColumnSpacing 55 | 36 56 | CreationDate 57 | 2011-01-10 10:27:48 -0500 58 | Creator 59 | Jim Weirich 60 | DisplayScale 61 | 1 0/72 in = 1 0/72 in 62 | GraphDocumentVersion 63 | 6 64 | GraphicsList 65 | 66 | 67 | Bounds 68 | {{377, 314}, {164, 54}} 69 | Class 70 | ShapedGraphic 71 | FitText 72 | YES 73 | Flow 74 | Resize 75 | FontInfo 76 | 77 | Font 78 | Helvetica 79 | Size 80 | 10 81 | 82 | ID 83 | 32 84 | Magnets 85 | 86 | {0, 1} 87 | {0, -1} 88 | {1, 0} 89 | {-1, 0} 90 | 91 | Shape 92 | RoundRect 93 | Style 94 | 95 | fill 96 | 97 | Draws 98 | NO 99 | 100 | shadow 101 | 102 | Draws 103 | NO 104 | 105 | stroke 106 | 107 | Draws 108 | NO 109 | 110 | 111 | Text 112 | 113 | Text 114 | {\rtf1\ansi\ansicpg1252\cocoartf949\cocoasubrtf540 115 | {\fonttbl\f0\fswiss\fcharset0 Helvetica;} 116 | {\colortbl;\red255\green255\blue255;} 117 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc 118 | 119 | \f0\fs36 \cf0 Copy of\ 120 | Original Repository} 121 | 122 | TextRelativeArea 123 | {{0, 0}, {1, 1}} 124 | Wrap 125 | NO 126 | 127 | 128 | Bounds 129 | {{49.4613, 325}, {164, 32}} 130 | Class 131 | ShapedGraphic 132 | FitText 133 | YES 134 | Flow 135 | Resize 136 | FontInfo 137 | 138 | Font 139 | Helvetica 140 | Size 141 | 10 142 | 143 | ID 144 | 31 145 | Magnets 146 | 147 | {0, 1} 148 | {0, -1} 149 | {1, 0} 150 | {-1, 0} 151 | 152 | Shape 153 | RoundRect 154 | Style 155 | 156 | fill 157 | 158 | Draws 159 | NO 160 | 161 | shadow 162 | 163 | Draws 164 | NO 165 | 166 | stroke 167 | 168 | Draws 169 | NO 170 | 171 | 172 | Text 173 | 174 | Text 175 | {\rtf1\ansi\ansicpg1252\cocoartf949\cocoasubrtf540 176 | {\fonttbl\f0\fswiss\fcharset0 Helvetica;} 177 | {\colortbl;\red255\green255\blue255;} 178 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc 179 | 180 | \f0\fs36 \cf0 Original Repository} 181 | 182 | TextRelativeArea 183 | {{0, 0}, {1, 1}} 184 | Wrap 185 | NO 186 | 187 | 188 | Bounds 189 | {{392.077, 123}, {148.923, 168}} 190 | Class 191 | ShapedGraphic 192 | FontInfo 193 | 194 | Font 195 | Arial-BoldMT 196 | Size 197 | 18 198 | 199 | ID 200 | 30 201 | Magnets 202 | 203 | {0, 1} 204 | {0, -1} 205 | {1, 0} 206 | {-1, 0} 207 | 208 | Shape 209 | Cylinder 210 | Style 211 | 212 | stroke 213 | 214 | Width 215 | 2 216 | 217 | 218 | Text 219 | 220 | Text 221 | {\rtf1\ansi\ansicpg1252\cocoartf949\cocoasubrtf540 222 | {\fonttbl\f0\fswiss\fcharset0 ArialMT;} 223 | {\colortbl;\red255\green255\blue255;} 224 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc 225 | 226 | \f0\b\fs36 \cf0 cloned_hello} 227 | VerticalPad 228 | 0 229 | 230 | 231 | 232 | Bounds 233 | {{229.437, 160}, {139.126, 94}} 234 | Class 235 | ShapedGraphic 236 | FontInfo 237 | 238 | Font 239 | Arial-BoldMT 240 | Size 241 | 18 242 | 243 | ID 244 | 29 245 | Shape 246 | AdjustableArrow 247 | ShapeData 248 | 249 | ratio 250 | 0.38095235824584961 251 | width 252 | 55.56268310546875 253 | 254 | Style 255 | 256 | fill 257 | 258 | Color 259 | 260 | b 261 | 1 262 | g 263 | 0.81593 264 | r 265 | 0.555535 266 | 267 | FillType 268 | 2 269 | GradientAngle 270 | 90 271 | GradientColor 272 | 273 | b 274 | 0.874372 275 | g 276 | 0.637537 277 | r 278 | 0.304356 279 | 280 | MiddleFraction 281 | 0.4523809552192688 282 | 283 | shadow 284 | 285 | Color 286 | 287 | a 288 | 0.4 289 | b 290 | 0 291 | g 292 | 0 293 | r 294 | 0 295 | 296 | ShadowVector 297 | {0, 2} 298 | 299 | stroke 300 | 301 | Color 302 | 303 | b 304 | 0.794995 305 | g 306 | 0.459724 307 | r 308 | 0 309 | 310 | Width 311 | 2 312 | 313 | 314 | Text 315 | 316 | Text 317 | {\rtf1\ansi\ansicpg1252\cocoartf949\cocoasubrtf540 318 | {\fonttbl\f0\fswiss\fcharset0 ArialMT;} 319 | {\colortbl;\red255\green255\blue255;} 320 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural 321 | 322 | \f0\b\fs36 \cf0 git clone} 323 | 324 | TextRelativeArea 325 | {{0.125, 0.25}, {0.75, 0.5}} 326 | isConnectedShape 327 | 328 | 329 | 330 | Bounds 331 | {{56.9998, 123}, {148.923, 168}} 332 | Class 333 | ShapedGraphic 334 | FontInfo 335 | 336 | Font 337 | Arial-BoldMT 338 | Size 339 | 18 340 | 341 | ID 342 | 10 343 | Magnets 344 | 345 | {0, 1} 346 | {0, -1} 347 | {1, 0} 348 | {-1, 0} 349 | 350 | Shape 351 | Cylinder 352 | Style 353 | 354 | stroke 355 | 356 | Width 357 | 2 358 | 359 | 360 | Text 361 | 362 | Text 363 | {\rtf1\ansi\ansicpg1252\cocoartf949\cocoasubrtf540 364 | {\fonttbl\f0\fswiss\fcharset0 ArialMT;} 365 | {\colortbl;\red255\green255\blue255;} 366 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc 367 | 368 | \f0\b\fs36 \cf0 hello} 369 | VerticalPad 370 | 0 371 | 372 | 373 | 374 | GridInfo 375 | 376 | GuidesLocked 377 | NO 378 | GuidesVisible 379 | YES 380 | HPages 381 | 1 382 | ImageCounter 383 | 1 384 | KeepToScale 385 | 386 | Layers 387 | 388 | 389 | Lock 390 | NO 391 | Name 392 | Layer 1 393 | Print 394 | YES 395 | View 396 | YES 397 | 398 | 399 | LayoutInfo 400 | 401 | Animate 402 | NO 403 | circoMinDist 404 | 18 405 | circoSeparation 406 | 0.0 407 | layoutEngine 408 | dot 409 | neatoSeparation 410 | 0.0 411 | twopiSeparation 412 | 0.0 413 | 414 | LinksVisible 415 | NO 416 | MagnetsVisible 417 | NO 418 | MasterSheets 419 | 420 | ModificationDate 421 | 2011-01-10 11:03:36 -0500 422 | Modifier 423 | Jim Weirich 424 | NotesVisible 425 | NO 426 | Orientation 427 | 2 428 | OriginVisible 429 | NO 430 | PageBreaks 431 | YES 432 | PrintInfo 433 | 434 | NSBottomMargin 435 | 436 | float 437 | 41 438 | 439 | NSLeftMargin 440 | 441 | float 442 | 18 443 | 444 | NSPaperSize 445 | 446 | size 447 | {612, 792} 448 | 449 | NSRightMargin 450 | 451 | float 452 | 18 453 | 454 | NSTopMargin 455 | 456 | float 457 | 18 458 | 459 | 460 | PrintOnePage 461 | 462 | ReadOnly 463 | NO 464 | RowAlign 465 | 1 466 | RowSpacing 467 | 36 468 | SheetTitle 469 | Canvas 1 470 | SmartAlignmentGuidesActive 471 | YES 472 | SmartDistanceGuidesActive 473 | YES 474 | UniqueID 475 | 1 476 | UseEntirePage 477 | 478 | VPages 479 | 1 480 | WindowInfo 481 | 482 | CurrentSheet 483 | 0 484 | ExpandedCanvases 485 | 486 | 487 | name 488 | Canvas 1 489 | 490 | 491 | Frame 492 | {{519, 79}, {891, 922}} 493 | ListView 494 | 495 | OutlineWidth 496 | 142 497 | RightSidebar 498 | 499 | ShowRuler 500 | 501 | Sidebar 502 | 503 | SidebarWidth 504 | 120 505 | VisibleRegion 506 | {{-90, -17}, {756, 768}} 507 | Zoom 508 | 1 509 | ZoomValues 510 | 511 | 512 | Canvas 1 513 | 1 514 | 1 515 | 516 | 517 | 518 | saveQuickLookFiles 519 | YES 520 | 521 | 522 | -------------------------------------------------------------------------------- /keynote/git_extra.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edgecase/git_immersion/18933d08e4a206b23454a2804ac4999e67571346/keynote/git_extra.key -------------------------------------------------------------------------------- /keynote/git_tutorial.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edgecase/git_immersion/18933d08e4a206b23454a2804ac4999e67571346/keynote/git_tutorial.key -------------------------------------------------------------------------------- /pdfs/git_tutorial.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edgecase/git_immersion/18933d08e4a206b23454a2804ac4999e67571346/pdfs/git_tutorial.pdf -------------------------------------------------------------------------------- /rakelib/labs.rake: -------------------------------------------------------------------------------- 1 | module Labs 2 | module_function 3 | 4 | HTML_DIR = 'git_tutorial/html' 5 | WORK_DIR = 'git_tutorial/work' 6 | 7 | class Lab 8 | attr_reader :name, :number, :lines 9 | attr_accessor :next, :prev 10 | 11 | def initialize(name, number) 12 | @name = name 13 | @number = number 14 | @lines = "" 15 | end 16 | 17 | def empty? 18 | @lines.empty? 19 | end 20 | 21 | def <<(line) 22 | @lines << line 23 | end 24 | 25 | def filename 26 | "lab_%02d.html" % number 27 | end 28 | 29 | def to_html 30 | RedCloth.new(lines).to_html 31 | end 32 | end 33 | 34 | def make_sample_name(lab_number, word) 35 | SAMPLES_DIR + ("/%03d_%s.txt" % [lab_number, word]) 36 | end 37 | 38 | def generate_labs(io) 39 | lab_index = -1 40 | step_index = 0 41 | labs = [] 42 | mode = :direct 43 | gathered_line = '' 44 | io.each do |line| 45 | next if line =~ /^\s*-+\s*$/ # omit dividers 46 | next if line =~ /^[+][a-z]/ # omit hidden commands 47 | line.sub!(/^[-!]/,'') # remove force and execute ignore chars 48 | case mode 49 | when :direct 50 | if line =~ /^h1.\s+(.+)$/ 51 | step_index = 0 52 | lab_index += 1 53 | lab = Lab.new($1, lab_index+1) 54 | lab.prev = labs.last 55 | labs.last.next = lab if labs.last 56 | line.sub!(/h1\./, "h1(lab_title). _lab #{lab_index+1}_ ") # add lab number 57 | line.sub!(/:/, ":
") # break long titles at the colon 58 | line.sub!(/\((?!lab_title)/, "
(") # break long titles at the last open paren 59 | lab.lines << line 60 | labs << lab 61 | elsif line =~ /^h2\./ 62 | step_index += 1 63 | labs[lab_index] << line.sub(/h2\.\s+(.+)/, "h2. \\1") 64 | elsif line =~ /^pre*\(.*\)\.\s*$/ 65 | mode = :gather1 66 | gathered_line = line.strip 67 | elsif line =~ /^p(\([(a-z){}]*)?\.\s+/ 68 | mode = :gather 69 | gathered_line = line.strip 70 | elsif line =~ /^Execute:$/i 71 | mode = :gather1 72 | labs[lab_index] << "h3. **Execute:**\n\n" 73 | gathered_line = "pre(instructions)." 74 | elsif line =~ /^File:\s+(\S+)$/i 75 | file_name = $1 76 | labs[lab_index] << "h3(file-heading). _#{file_name}_\n\n" 77 | gathered_line = "
"
 78 |           mode = :file
 79 |         elsif line =~ /^Output:\s*$/
 80 |           labs[lab_index] << "h3. **Output:**\n\n"
 81 |           gathered_line = "
"
 82 |           mode = :file
 83 |         elsif line =~ /^Set: +\w+=.*$/
 84 |           # Skip set lines
 85 |         elsif line =~ /^Freeze\s*$/
 86 |           # Skip freeze lines
 87 |         elsif line =~ /^=\w+/
 88 |           # Skip include lines
 89 |         else
 90 |           labs[lab_index] << line unless lab_index < 0
 91 |         end
 92 |       when :gather1
 93 |         labs[lab_index] << gathered_line << " " << line
 94 |         mode = :direct
 95 |       when :gather
 96 |         if line =~ /^\s*$/
 97 |           labs[lab_index] << gathered_line << "\n\n"
 98 |           mode = :direct
 99 |         else
100 |           gathered_line << " " << line.strip
101 |         end
102 |       when :file
103 |         if line =~ /^EOF$/
104 |           labs[lab_index] << "
\n" 105 | mode = :direct 106 | elsif line =~ /^=(\w+)/ 107 | sample_name = make_sample_name(lab_index+1, $1) 108 | open(sample_name) do |ins| 109 | ins.each do |sample_line| 110 | labs[lab_index] << "#{gathered_line}#{sample_line}" 111 | gathered_line = '' 112 | end 113 | end 114 | else 115 | labs[lab_index] << "#{gathered_line}#{line}" 116 | gathered_line = '' 117 | end 118 | end 119 | end 120 | write_index_html(labs) 121 | labs.each do |lab| 122 | write_lab_html(lab, labs) 123 | end 124 | end 125 | 126 | def nav_links(f, lab) 127 | partial("nav", binding) 128 | end 129 | 130 | def lab_index(f, labs) 131 | partial("lab_index", binding) 132 | end 133 | 134 | def partial(template, bnd) 135 | result = open("templates/#{template}.html.erb") do |tpl| 136 | template_string = tpl.read 137 | template_string.gsub!(/-%>/, "%>@NONEWLINE@") 138 | ERB.new(template_string).result(bnd) 139 | end 140 | result.gsub(/@NONEWLINE@\n/, '') 141 | end 142 | 143 | def write_index_html(labs) 144 | File.open("#{HTML_DIR}/index.html", "w") do |f| 145 | f.puts partial('index', binding) 146 | end 147 | end 148 | 149 | def write_lab_html(lab, labs) 150 | lab_html = lab.to_html 151 | File.open("#{HTML_DIR}/#{lab.filename}", "w") { |f| 152 | f.puts partial('lab', binding) 153 | } 154 | end 155 | end 156 | 157 | require 'rubygems' 158 | require 'redcloth' 159 | require 'rake/clean' 160 | 161 | CLOBBER.include(Labs::HTML_DIR) 162 | CLOBBER.include(Labs::WORK_DIR) 163 | 164 | directory Labs::HTML_DIR 165 | directory Labs::WORK_DIR 166 | 167 | TO_HTML = "#{Labs::HTML_DIR}/%f" 168 | EXTRA_SRCS = FileList['src/*.css', 'src/*.js', 'src/*.gif', 'src/*.jpg', 'src/*.png', 'src/*.eot', 'src/*.ttf', 'src/*.woff', 'diagrams/*.png', 'src/manifest.json'] 169 | EXTRA_OUT = EXTRA_SRCS.pathmap(TO_HTML) 170 | INDEX_HTML = File.join(Labs::HTML_DIR, "index.html") 171 | 172 | TEMPLATE_FILES = FileList['templates/*.erb'] 173 | 174 | task :extra => EXTRA_OUT 175 | 176 | EXTRA_SRCS.each do |extra| 177 | file extra.pathmap(TO_HTML) => [Labs::HTML_DIR, extra] do |t| 178 | cp extra, t.name 179 | end 180 | end 181 | 182 | file INDEX_HTML => [Labs::HTML_DIR, Labs::WORK_DIR, "src/labs.txt", "rakelib/labs.rake", SAMPLE_TAG] + EXTRA_OUT + TEMPLATE_FILES do |t| 183 | puts "Generating HTML" 184 | File.open("src/labs.txt") { |f| Labs.generate_labs(f) } 185 | end 186 | 187 | desc "Create the Lab HTML" 188 | task :labs => [INDEX_HTML] 189 | 190 | desc "View the Labs" 191 | task :view do 192 | sh "open #{Labs::HTML_DIR}/index.html" 193 | end 194 | -------------------------------------------------------------------------------- /rakelib/run.rake: -------------------------------------------------------------------------------- 1 | require "pathname" 2 | 3 | module RunLabs 4 | module_function 5 | 6 | def make_sample_name(lab_number, word) 7 | SAMPLES_DIR + ("/%03d_%s.txt" % [lab_number, word]) 8 | end 9 | 10 | def run_command(command, var) 11 | command.gsub!(/<(\w+)>/) { |args, one| var[$1] } 12 | if command =~ /^cd +(\S+)$/ 13 | dir = $1 14 | puts "Changing to '#{dir}'" 15 | Dir.chdir(dir) 16 | @command_output = "$ #{command}" 17 | else 18 | output = `#{command.strip} 2>&1` 19 | if output.include?(Dir.home) 20 | auto_dir = Pathname.new(__FILE__).join('../../auto').to_s 21 | jims_path = '/Users/jim/Downloads/git_tutorial/work' 22 | jims_output = output.gsub(auto_dir, jims_path) 23 | @command_output = "$ #{command}#{jims_output}" 24 | else 25 | @command_output = "$ #{command}#{output}" 26 | end 27 | print output 28 | end 29 | end 30 | 31 | def hash_for(pattern) 32 | hash = `git log --pretty=oneline`.split(/\n/).grep(/#{pattern}/).first[0,7] rescue "" 33 | fail "Invalid hash (#{hash}) for '#{pattern}'" unless hash =~ /^[0-9a-zA-Z]{7}$/ 34 | hash 35 | end 36 | 37 | def hash_in(hash, pattern) 38 | lines = `git cat-file -p #{hash}`.split(/\n/) 39 | line = lines.grep(/#{pattern}/).first || "" 40 | md = /[0-9a-zA-Z]{40}/.match(line) 41 | fail "No hash found for /#{pattern}/ while dumping '#{hash}'" if md.nil? 42 | md[0][0,7] 43 | end 44 | 45 | def freeze_lab(lab_number) 46 | repo_name = ("lab_%02d" % (lab_number+1)) 47 | FileUtils.rm_r "#{REPOS_DIR}/#{repo_name}" rescue nil 48 | FileUtils.cp_r '.', "#{REPOS_DIR}/#{repo_name}" 49 | puts "FREEZING: #{repo_name}" 50 | end 51 | 52 | def run_labs(source, last_lab) 53 | last_lab = last_lab.to_i if last_lab 54 | var = {} 55 | output_file = nil 56 | state = :seek 57 | lab_number = 0 58 | @command_output = "" 59 | while line = source.gets 60 | case state 61 | when :seek 62 | if line =~ /^Execute: *$/ 63 | puts "EXECUTE" 64 | state = :execute 65 | elsif line =~ /^File: (\S+) *$/ 66 | fn = $1 67 | puts "Create #{fn}" 68 | output_file = open(fn, "w") 69 | state = :file 70 | elsif line =~ /^-------------* *$/ 71 | freeze_lab(lab_number) if (lab_number+1) % 5 == 0 72 | if last_lab && lab_number >= last_lab 73 | puts 74 | puts "** Stopping at Lab #{lab_number} **" 75 | puts 76 | break 77 | end 78 | lab_number += 1 79 | puts "** Lab #{lab_number} **********************************************************" 80 | elsif line =~ /^Set: +(\w+)=(.*)$/ 81 | vname = $1 82 | code = $2 83 | var[vname] = eval(code) 84 | puts "SETTING: #{vname}='#{var[vname]}' (from #{code})" 85 | elsif line =~ /^Freeze\s*$/ 86 | freeze_lab(lab_number) 87 | else 88 | puts " #{line}" 89 | end 90 | when :file 91 | if line =~ /^EOF *$/ 92 | output_file.close if output_file 93 | output_file = nil 94 | state = :seek 95 | else 96 | output_file.puts(line) 97 | puts "CONTENT: #{line}" 98 | end 99 | when :execute 100 | if line =~ /^ *$/ 101 | state = :seek 102 | elsif line =~ /^=(\w+)/ 103 | name = $1 104 | puts "CAPTURING: #{name}" 105 | sample_name = make_sample_name(lab_number, name) 106 | open(sample_name, "w") do |out| 107 | out.write(@command_output) 108 | end 109 | elsif line =~ /^!/ 110 | line.sub!(/^!/,'') 111 | puts "RUNNING: <#{line.strip}>" 112 | run_command(line, var) rescue nil 113 | elsif line =~ /^-/ 114 | puts "SKIPPING: <#{line.strip}>" 115 | else 116 | puts "RUNNING: <#{line.strip}>" 117 | line.sub!(/^\+/, '') 118 | run_command(line, var) 119 | end 120 | end 121 | end 122 | ensure 123 | output_file.close if output_file 124 | end 125 | 126 | def build 127 | build_to(nil) 128 | end 129 | 130 | def build_to(last_lab=nil) 131 | old_dir = Dir.pwd 132 | FileUtils.rm_r "auto" rescue nil 133 | FileUtils.mkdir_p "auto" 134 | open("src/labs.txt") do |lab_source| 135 | Dir.chdir "auto" 136 | RunLabs.run_labs(lab_source, last_lab) 137 | end 138 | FileUtils.touch SAMPLE_TAG 139 | ensure 140 | Dir.chdir(old_dir) 141 | end 142 | end 143 | 144 | 145 | directory SAMPLES_DIR 146 | directory REPOS_DIR 147 | 148 | file SAMPLE_TAG => ["src/labs.txt", SAMPLES_DIR, REPOS_DIR] do 149 | RunLabs.build 150 | end 151 | 152 | desc "Run the labs if needed" 153 | task :build => SAMPLE_TAG 154 | 155 | desc "Run the labs automatically" 156 | task :run, [:last_lab] => [SAMPLES_DIR, REPOS_DIR] do |t, args| 157 | RunLabs.build_to(args.last_lab) 158 | end 159 | -------------------------------------------------------------------------------- /run: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | DIR=$PWD 4 | while [ ! -r keynote ]; do 5 | cd .. 6 | done 7 | 8 | rake run[$1] 9 | 10 | echo RETURNING TO $DIR 11 | cd $DIR 12 | -------------------------------------------------------------------------------- /src/UI.js: -------------------------------------------------------------------------------- 1 | function ReplaceWithPolyfill() { 2 | 'use-strict'; // For safari, and IE > 10 3 | var parent = this.parentNode, i = arguments.length, currentNode; 4 | if (!parent) return; 5 | if (!i) // if there are no arguments 6 | parent.removeChild(this); 7 | while (i--) { // i-- decrements i and returns the value of i before the decrement 8 | currentNode = arguments[i]; 9 | if (typeof currentNode !== 'object'){ 10 | currentNode = this.ownerDocument.createTextNode(currentNode); 11 | } else if (currentNode.parentNode){ 12 | currentNode.parentNode.removeChild(currentNode); 13 | } 14 | // the value of "i" below is after the decrement 15 | if (!i) // if currentNode is the first argument (currentNode === arguments[0]) 16 | parent.replaceChild(currentNode, this); 17 | else // if currentNode isn't the first 18 | parent.insertBefore(this.previousSibling, currentNode); 19 | } 20 | } 21 | if (!Element.prototype.replaceWith) 22 | Element.prototype.replaceWith = ReplaceWithPolyfill; 23 | if (!CharacterData.prototype.replaceWith) 24 | CharacterData.prototype.replaceWith = ReplaceWithPolyfill; 25 | if (!DocumentType.prototype.replaceWith) 26 | DocumentType.prototype.replaceWith = ReplaceWithPolyfill; 27 | 28 | 29 | window.scrollTo = function(element, to, duration) { 30 | var start = element.scrollTop, 31 | change = to - start, 32 | currentTime = 0, 33 | increment = 20; 34 | 35 | var animateScroll = function(){ 36 | currentTime += increment; 37 | var val = Math.easeInOutQuad(currentTime, start, change, duration); 38 | element.scrollTop = val; 39 | if(currentTime < duration) { 40 | setTimeout(animateScroll, increment); 41 | } 42 | }; 43 | animateScroll(); 44 | } 45 | 46 | //t = current time 47 | //b = start value 48 | //c = change in value 49 | //d = duration 50 | Math.easeInOutQuad = function (t, b, c, d) { 51 | t /= d/2; 52 | if (t < 1) return c/2*t*t + b; 53 | t--; 54 | return -c/2 * (t*(t-2) - 1) + b; 55 | }; 56 | 57 | var scrollToActiveNavItem = function() { 58 | var nav = document.querySelector('#index nav'); 59 | 60 | nav.focus(); 61 | scrollTo( 62 | document.querySelector('.layout'), 63 | document.querySelector('nav .active').offsetTop, 64 | 550 65 | ); 66 | }; 67 | 68 | 69 | document.addEventListener('DOMContentLoaded', function() { 70 | var fallbackCopyTextToClipboard = function(text, callback) { 71 | var textarea = document.createElement('textarea'); 72 | document.body.appendChild(textarea); 73 | textarea.value = text; 74 | textarea.focus(); 75 | textarea.select(); 76 | 77 | try { 78 | document.execCommand('copy'); 79 | callback(); 80 | } catch (err) { 81 | console.error('Oops, unable to copy', err); 82 | } 83 | 84 | document.body.removeChild(textarea); 85 | }; 86 | 87 | var copyTextToClipboard = function(text, callback) { 88 | if (!navigator.clipboard) { 89 | fallbackCopyTextToClipboard(text, callback); 90 | return; 91 | } 92 | navigator.clipboard.writeText(text).then( 93 | function() { callback(); }, 94 | function(err) { 95 | console.error('Oops, unable to copy', err); 96 | } 97 | ); 98 | }; 99 | 100 | // mobile nav "menu" button 101 | document 102 | .querySelector('.link-menu') 103 | .addEventListener('click', function(e) { 104 | var nav = document.querySelector('#index nav'); 105 | nav.classList.toggle('open'); 106 | 107 | if(nav.classList.contains('open')) { 108 | setTimeout(scrollToActiveNavItem, 200); 109 | } 110 | }); 111 | 112 | // wrap terminal commands in div.instructions 113 | document.querySelectorAll('.instructions').forEach(function(pre, i) { 114 | var lines = pre.innerHTML.split('\n'); 115 | var container = document.createElement('div'); 116 | container.classList.add('instructions'); 117 | 118 | lines.forEach(function(line, i) { 119 | var pre = document.createElement('pre'); 120 | pre.innerHTML = line; 121 | container.appendChild(pre); 122 | }); 123 | 124 | pre.replaceWith(container); 125 | }); 126 | 127 | // copy terminal command to clipboard 128 | document 129 | .querySelectorAll('.instructions pre') 130 | .forEach(function(i) { 131 | i.setAttribute('tabindex', '0'); 132 | i.setAttribute('title', 'Click to copy'); 133 | i.classList.add('clickable'); 134 | i.addEventListener('click', function() { 135 | copyTextToClipboard(i.textContent.trim(), function() { 136 | i.focus(); 137 | }); 138 | }); 139 | }); 140 | 141 | // copy file contents to clipboard 142 | document 143 | .querySelectorAll('.file-heading') 144 | .forEach(function(heading) { 145 | var next = heading.nextElementSibling; 146 | if(next.nodeName.toLowerCase() === 'pre' && next.classList.contains('file')) { 147 | next.setAttribute('tabindex', 0); 148 | heading.setAttribute('title', 'Click to copy'); 149 | heading.classList.add('clickable'); 150 | heading.addEventListener('click', function() { 151 | copyTextToClipboard(next.textContent.trim(), function() { 152 | next.focus(); 153 | }); 154 | }); 155 | } 156 | }); 157 | 158 | // Add active link to current page in nav 159 | var labID = document.body.getAttribute('data-lab-id'); 160 | document.querySelector('#lab_' + labID + '_link a').classList.add('active'); 161 | }); 162 | -------------------------------------------------------------------------------- /src/config.rb: -------------------------------------------------------------------------------- 1 | # Require any additional compass plugins here. 2 | 3 | # Set this to the root of your project when deployed: 4 | http_path = "/" 5 | css_dir = "./" 6 | sass_dir = "./" 7 | images_dir = "./" 8 | javascripts_dir = "./" 9 | 10 | # You can select your preferred output style here (can be overridden via the command line): 11 | # output_style = :expanded or :nested or :compact or :compressed 12 | 13 | # To enable relative paths to assets via compass helper functions. Uncomment: 14 | # relative_assets = true 15 | 16 | # To disable debugging comments that display the original location of your selectors. Uncomment: 17 | # line_comments = false 18 | 19 | 20 | # If you prefer the indented syntax, you might want to regenerate this 21 | # project again passing --syntax sass, or you can uncomment this: 22 | # preferred_syntax = :sass 23 | # and then run: 24 | # sass-convert -R --from scss --to sass sass scss && rm -rf sass && mv scss sass 25 | -------------------------------------------------------------------------------- /src/git_clone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edgecase/git_immersion/18933d08e4a206b23454a2804ac4999e67571346/src/git_clone.png -------------------------------------------------------------------------------- /src/home.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 10vw 0 0 14vw; 3 | color: #333; 4 | } 5 | 6 | h1, p { width: 86%; } 7 | 8 | @media all and (min-width: 1000px) { 9 | h1, p { 10 | width: 57%; 11 | max-width: 750px; 12 | } 13 | } 14 | 15 | h1 { 16 | font-size: 24px; 17 | } 18 | 19 | p { 20 | font-size: 18px; 21 | line-height: 150%; 22 | } 23 | 24 | h1 + p em { 25 | font-family: "SF Mono", "SFMono-Regular", "Roboto Mono", Consolas, "Liberation Mono", Menlo, Courier, monospace; 26 | font-size: 24px; 27 | font-style: normal; 28 | line-height: 160%; 29 | } 30 | 31 | @media all and (max-width: 550px) { 32 | h1 + p em { 33 | font-size: 18px; 34 | } 35 | } 36 | 37 | hr { 38 | margin: 40px 0; 39 | border-top: 1px solid #000; 40 | } 41 | 42 | a { 43 | color: blue; 44 | } 45 | 46 | ul { 47 | margin-bottom: 30px; 48 | list-style-type: none; 49 | padding: 0; 50 | line-height: 200%; 51 | } 52 | 53 | .start { 54 | padding: 12px 30px; 55 | border-radius: 3px; 56 | color: #fff; 57 | font-family: "SF Mono", "SFMono-Regular", "Roboto Mono", Consolas, "Liberation Mono", Menlo, Courier, monospace; 58 | text-decoration: none; 59 | background: blue; 60 | } 61 | 62 | .footer { 63 | padding-top: 100px; 64 | } 65 | 66 | .footer a { 67 | color: #333; 68 | } 69 | 70 | @media all and (max-width: 550px) { 71 | body { padding-bottom: 60px; } 72 | 73 | .footer { padding-top: 0; } 74 | 75 | .start { 76 | position: fixed; 77 | right: 0; 78 | bottom: 0; 79 | left: 0; 80 | height: 60px; 81 | border-radius: 0; 82 | padding: 0; 83 | line-height: 60px; 84 | text-align: center; 85 | } 86 | } -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Git Immersion - Brought to you by EdgeCase 5 | 6 | 7 | 8 | 9 | 19 | 20 | 21 | 22 | 23 | 34 |
35 |
36 |
37 |

Git is a powerful, sophisticated system for distributed version control. Gaining an understanding of its features opens to developers a new and liberating approach to source code management. The surest path to mastering Git is to immerse oneself in its utilities and operations, to experience it first-hand.

38 |

Git Immersion is a guided tour that walks through the fundamentals of Git, inspired by the premise that to know a thing is to do it.

39 |
40 |
41 |

Install Git:

42 |
43 |

Mac

44 |

Git for Mac OS X

45 |
46 |
47 |

Windows

48 |

msysgit

49 |
50 |
51 |
52 |

Graphical Clients:

53 |
54 |

Mac

55 |

GitX

56 |
57 |
58 |

Windows

59 |

TortoiseGit

60 |
61 |
62 |
63 |
64 |
65 |

Other Resources

66 |

67 |

73 |

74 |
75 |
76 |

LICENSE

77 |

78 | GitImmersion is released under 79 | a Creative 80 | Commons, Attribution-NonCommercial-ShareAlike License. 81 |

82 |
83 |
84 |
85 |

Brought to you by:

86 | Neo 87 |
88 |
89 |
90 | 146 |
147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /src/lab_01.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Lab 1 - Git Immersion - Brought to you by EdgeCase 5 | 6 | 7 | 8 | 9 | 19 | 20 | 21 | 22 | 23 | 24 | 35 | 36 |
37 |

lab 1 Setup

38 |

Goals

39 |
    40 |
  • To setup git so that it is ready for work.
  • 41 |
42 |

Setup Name and Email 01

43 |

If you have never used git before, you need to do some setup first. Run the following commands so that git knows your name and email. If you have git already setup, you can skip down to the line ending section.

44 |

Execute:

45 |
git config --global user.name "Your Name"
 46 |       git config --global user.email "your_email@whatever.com"
47 |

Setup Line Ending Preferences 02

48 |

Also, for Unix/Mac users:

49 |

Execute:

50 |
git config --global core.autocrlf input
 51 |       git config --global core.safecrlf true
52 |

And for Windows users:

53 |

Execute:

54 |
git config --global core.autocrlf true
 55 |       git config --global core.safecrlf true
56 |
57 | 58 |
59 |

Table of Contents

60 |
61 |
62 | 118 |
119 | 120 | 121 | -------------------------------------------------------------------------------- /src/labs.txt: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------- 2 | h1. Setup 3 | 4 | h2. Goals 5 | 6 | * To setup git and ruby so that it is ready for work. 7 | 8 | h2. Setup Name and Email 9 | 10 | p. If you have never used git before, you need to do some setup first. 11 | Run the following commands so that git knows your name and email. If 12 | you have git already setup, you can skip down to the line ending 13 | section. 14 | 15 | execute: 16 | git config --global user.name "Your Name" 17 | git config --global user.email "your_email@whatever.com" 18 | 19 | h2. Setup Line Ending Preferences 20 | 21 | Also, for Unix/Mac users: 22 | 23 | execute: 24 | git config --global core.autocrlf input 25 | git config --global core.safecrlf true 26 | 27 | And for Windows users: 28 | 29 | execute: 30 | git config --global core.autocrlf true 31 | git config --global core.safecrlf true 32 | 33 | h2. Setup Ruby 34 | 35 | p. For this tutorial you need a working Ruby interpreter. If you haven´t one 36 | installed yet, it is time to set it up now: 37 | 38 | "https://www.ruby-lang.org/en/downloads/":https://www.ruby-lang.org/en/downloads/ 39 | 40 | ---------------------------------------------------------------------- 41 | h1. More Setup 42 | 43 | h2. Goals 44 | 45 | * Get the tutorial materials setup and ready to run. 46 | 47 | h2. Get the Tutorial package 48 | 49 | * Download from: "https://gitimmersion.com/git_tutorial.zip":https://gitimmersion.com/git_tutorial.zip 50 | 51 | h2. Unzip the tutorial 52 | 53 | p. The tutorial package should have a main directory "git_tutorial" 54 | with three sub-directories: 55 | 56 | * html -- These html files. Point your browser to html/index.html 57 | * work -- An empty working directory. Create your repos in here. 58 | * repos -- Prepackaged Git repositories so you can jump into the tutorial at any point. If you get stuck, just copy the desired lab into your working directory. 59 | 60 | ---------------------------------------------------------------------- 61 | h1. Create a Project 62 | 63 | h2. Goals 64 | 65 | * Learn how to create a git repository from scratch. 66 | 67 | h2. Create a "Hello, World" program 68 | 69 | p. Starting in the empty working directory, create an empty directory 70 | named "hello", then create a file named @hello.rb@ with the contents 71 | below. 72 | 73 | Execute: 74 | mkdir hello 75 | cd hello 76 | 77 | File: hello.rb 78 | puts "Hello, World" 79 | EOF 80 | 81 | h2. Create the Repository 82 | 83 | p. You now have a directory with a single file. To create a git 84 | repository from that directory, run the git init command. 85 | 86 | Execute: 87 | git init 88 | =init 89 | +git config user.name "Jim Weirich" 90 | +git config user.email "jim (at) edgecase.com" 91 | 92 | Output: 93 | =init 94 | EOF 95 | 96 | h2. Add the program to the repository 97 | 98 | Now let's add the "Hello, World" program to the repository. 99 | 100 | Execute: 101 | git add hello.rb 102 | =add 103 | git commit -m "First Commit" 104 | =commit 105 | 106 | p. You should see ... 107 | 108 | Output: 109 | =add 110 | =commit 111 | EOF 112 | 113 | ---------------------------------------------------------------------- 114 | h1. Checking Status 115 | 116 | h2. Goals 117 | 118 | * Learn how to check the status of the repository 119 | 120 | h2. Check the status of the repository 121 | 122 | p. Use the @git status@ command to check the current status of the 123 | repository. 124 | 125 | Execute: 126 | git status 127 | =status 128 | 129 | p. You should see 130 | 131 | Output: 132 | =status 133 | EOF 134 | 135 | p. The status command reports that there is nothing to commit. This 136 | means that the repository has all the current state of the working 137 | directory. There are no outstanding changes to record. 138 | 139 | p. We will use the @git status@ command to continue to monitor the 140 | state between the repository and the working directory. 141 | 142 | ---------------------------------------------------------------------- 143 | h1. Making Changes 144 | 145 | h2. Goals 146 | 147 | * Learn how to monitor the state of the working directory 148 | 149 | h2. Change the "Hello, World" program. 150 | 151 | p. It's time to change our hello program to take an argument from the 152 | command line. Change the file to be: 153 | 154 | File: hello.rb 155 | puts "Hello, #{ARGV.first}!" 156 | EOF 157 | 158 | h2. Check the status 159 | 160 | p. Now check the status of the working directory. 161 | 162 | Execute: 163 | !git status 164 | =status 165 | 166 | p. You should see ... 167 | 168 | Output: 169 | =status 170 | EOF 171 | 172 | p. The first thing to notice is that git knows that the @hello.rb@ 173 | file has been modified, but git has not yet been notified of these 174 | changes. 175 | 176 | p. Also notice that the status message gives you hints about what you 177 | need to do next. If you want to add these changes to the repository, 178 | then use the @git add@ command. Otherwise the @git restore@ command 179 | can be used to discard the changes. 180 | 181 | h2. Up Next 182 | 183 | p. Let's stage the change. 184 | 185 | ---------------------------------------------------------------------- 186 | h1. Staging Changes 187 | 188 | h2. Goals 189 | 190 | * Learn how to stage changes for later commits 191 | 192 | h2. Add Changes 193 | 194 | p. Now tell git to stage the changes. Check the status 195 | 196 | Execute: 197 | git add hello.rb 198 | =add 199 | git status 200 | =status 201 | 202 | p. You should see ... 203 | 204 | Output: 205 | =add 206 | =status 207 | EOF 208 | 209 | p. The change to the @hello.rb@ file has been staged. This means that 210 | git now knows about the change, but the change hasn't been 211 | _permanently_ recorded in the repository yet. The next commit 212 | operation will include the staged changes. 213 | 214 | p. If you decide you _don't_ want to commit that change after all, the 215 | status command reminds you that the @git restore@ command can be used to 216 | unstage that change. 217 | 218 | ---------------------------------------------------------------------- 219 | h1. Staging and Committing 220 | 221 | p. A separate staging step in git is in line with the philosophy of 222 | getting out of the way until you need to deal with source control. 223 | You can continue to make changes to your working directory, and then 224 | at the point you want to interact with source control, git allows you 225 | to record your changes in small commits that record exactly what you 226 | did. 227 | 228 | p. For example, suppose you edited three files (@a.rb@, @b.rb@, and 229 | @c.rb@). Now you want to commit all the changes, but you want the 230 | changes in @a.rb@ and @b.rb@ to be a single commit, while the changes 231 | to @c.rb@ are not logically related to the first two files and should 232 | be a separate commit. 233 | 234 | p. You could do the following: 235 | 236 | pre(instructions). 237 | git add a.rb 238 | git add b.rb 239 | git commit -m "Changes for a and b" 240 | 241 | pre(instructions). 242 | git add c.rb 243 | git commit -m "Unrelated change to c" 244 | 245 | p. By separating staging and committing, you have the ability to 246 | easily fine tune what goes into each commit. 247 | 248 | ---------------------------------------------------------------------- 249 | h1. Committing Changes 250 | 251 | h2. Goals 252 | 253 | * Learn how to commit changes to the repository 254 | 255 | h2. Commit the change 256 | 257 | p. Ok, enough about staging. Let's commit what we have staged to the 258 | repository. 259 | 260 | p. When you used @git commit@ previously to commit the initial version 261 | of the @hello.rb@ file to the repository, you included the @-m@ flag 262 | that gave a comment on the command line. The commit command will 263 | allow you to interactively edit a comment for the commit. Let's try 264 | that now. 265 | 266 | p. If you omit the @-m@ flag from the command line, git will pop you 267 | into the editor of your choice. The editor is chosen from the 268 | following list (in priority order): 269 | 270 | * GIT_EDITOR environment variable 271 | * core.editor configuration setting 272 | * VISUAL environment variable 273 | * EDITOR environment variable 274 | 275 | p. I have the EDITOR variable set to @emacsclient@. 276 | 277 | p. So commit now and check the status. 278 | 279 | Execute: 280 | -git commit 281 | +git commit -m "Using ARGV" 282 | 283 | p. You should see the following in your editor: 284 | 285 | Output: 286 | | 287 | # Please enter the commit message for your changes. Lines starting 288 | # with '#' will be ignored, and an empty message aborts the commit. 289 | # On branch main 290 | # Changes to be committed: 291 | # (use "git reset HEAD ..." to unstage) 292 | # 293 | # modified: hello.rb 294 | # 295 | EOF 296 | 297 | p. On the first line, enter the comment: "Using ARGV". Save the file 298 | and exit the editor. You should see ... 299 | 300 | Output: 301 | git commit 302 | Waiting for Emacs... 303 | [main 569aa96] Using ARGV 304 | 1 files changed, 1 insertions(+), 1 deletions(-) 305 | EOF 306 | 307 | p. The "Waiting for Emacs..." line comes from the @emacsclient@ 308 | program which sends the file to a running emacs program and waits for 309 | the file to be closed. The rest of the output is the standard commit 310 | messages. 311 | 312 | h2. Check the status 313 | 314 | p. Finally let's check the status again. 315 | 316 | Execute: 317 | !git status 318 | =status 319 | 320 | You should see ... 321 | 322 | Output: 323 | =status 324 | EOF 325 | 326 | p. The working directory is clean and ready for you to continue. 327 | 328 | ---------------------------------------------------------------------- 329 | h1. Changes, not Files 330 | 331 | h2. Goals 332 | 333 | * Learn that git works with changes, not files. 334 | 335 | p. Most source control systems work with files. You add a file to 336 | source control and the system will track changes to the file from that 337 | point on. 338 | 339 | p. Git focuses on the changes to a file rather than the file itself. 340 | When you say @git add file@, you are not telling git to add the file 341 | to the repository. Rather you are saying that git should make note of 342 | the current state of that file to be committed later. 343 | 344 | p. We will attempt to explore that difference in this lab. 345 | 346 | h2. First Change: Allow a default name 347 | 348 | p. Change the "Hello, World" program to have a default value if a 349 | command line argument is not supplied. 350 | 351 | File: hello.rb 352 | name = ARGV.first || "World" 353 | 354 | puts "Hello, #{name}!" 355 | EOF 356 | 357 | h2. Add this Change 358 | 359 | p. Now add this change to the git's staging area. 360 | 361 | Execute: 362 | git add hello.rb 363 | 364 | h2. Second change: Add a comment 365 | 366 | p. Now add a comment to the "Hello, World" program. 367 | 368 | File: hello.rb 369 | # Default is "World" 370 | name = ARGV.first || "World" 371 | 372 | puts "Hello, #{name}!" 373 | EOF 374 | 375 | h2. Check the current status 376 | 377 | Execute: 378 | !git status 379 | =status 380 | 381 | p. You should see ... 382 | 383 | Output: 384 | =status 385 | EOF 386 | 387 | p. Notice how @hello.rb@ is listed twice in the status. The first 388 | change (adding a default) is staged and is ready to be committed. The 389 | second change (adding a comment) is unstaged. If you were to commit 390 | right now, the comment would not be saved in the repository. 391 | 392 | p. Let's try that. 393 | 394 | h2. Committing 395 | 396 | p. Commit the staged change (the default value), and then recheck the 397 | status. 398 | 399 | Execute: 400 | git commit -m "Added a default value" 401 | =commit 402 | git status 403 | =status2 404 | 405 | p. You should see ... 406 | 407 | Output: 408 | =commit 409 | =status2 410 | EOF 411 | 412 | p. The status command is telling you that @hello.rb@ has unrecorded changes, 413 | but is no longer in the staging area. 414 | 415 | h2. Add the Second Change 416 | 417 | p. Now add the second change to staging area, then run git status. 418 | 419 | Execute: 420 | git add . 421 | !git status 422 | =status3 423 | 424 | p(note). *Note:* We used the current directory ('.') as the 425 | file to add. This is a really convenient shortcut for adding in all 426 | the changes to the files in the current directory and below. But 427 | since it adds everything, it is a _really_ good idea to check the status 428 | before doing an add ., just to make sure you don't add any 429 | file that is not intended. 430 | 431 | p(note). I wanted you to see the "add ." trick, but we will 432 | continue to add explicit files in the rest of this tutorial just to be 433 | safe. 434 | 435 | p. You should see ... 436 | 437 | Output: 438 | =status3 439 | EOF 440 | 441 | p. Now the second change has been staged and is ready to commit. 442 | 443 | h2. Commit the Second Change 444 | 445 | Execute: 446 | git commit -m "Added a comment" 447 | 448 | ---------------------------------------------------------------------- 449 | h1. History 450 | 451 | h2. Goals 452 | 453 | * Learn how to view the history of the project. 454 | 455 | p. Getting a listing of what changes have been made is the function of 456 | the @git log@ command. 457 | 458 | Execute: 459 | git log 460 | =log 461 | 462 | p. You should see ... 463 | 464 | Output: 465 | =log 466 | EOF 467 | 468 | p. Here is a list of all four commits that we have made to the 469 | repository so far. 470 | 471 | h2. One Line Histories 472 | 473 | p. You have a great deal of control over exactly what the @log@ command 474 | displays. I like the one line format: 475 | 476 | Execute: 477 | git log --pretty=oneline 478 | =oneline 479 | 480 | p. You should see ... 481 | 482 | Output: 483 | =oneline 484 | EOF 485 | 486 | h2. Controlling Which Entries are Displayed 487 | 488 | p. There are a lot of options for selecting which entries are 489 | displayed in the log. Play around with the following options: 490 | 491 | pre(instructions). 492 | git log --pretty=oneline --max-count=2 493 | git log --pretty=oneline --since='5 minutes ago' 494 | git log --pretty=oneline --until='5 minutes ago' 495 | git log --pretty=oneline --author= 496 | git log --pretty=oneline --all 497 | 498 | p. See man git-log for all the details. 499 | 500 | h2. Getting Fancy 501 | 502 | p. Here's what I use to review the changes made in the last week. 503 | I'll add @--author=jim@ if I only want to see changes I made. 504 | 505 | pre(instructions). 506 | git log --all --pretty=format:'%h %cd %s (%an)' --since='7 days ago' 507 | 508 | h2. The Ultimate Log Format 509 | 510 | p. Over time, I've decided that I like the following log format for 511 | most of my work. 512 | 513 | Execute: 514 | git log --pretty=format:'%h %ad | %s%d [%an]' --graph --date=short 515 | =ultimate 516 | 517 | It looks like this: 518 | 519 | Output: 520 | =ultimate 521 | EOF 522 | 523 | p. Let's look at it in detail: 524 | 525 | * @--pretty=format:"..."@ defines the format of the output. 526 | * @%h@ is the abbreviated hash of the commit 527 | * @%ad@ is the author date 528 | * @%s@ is the comment 529 | * @%d@ are any decorations on that commit (e.g. branch heads or tags) 530 | * @%an@ is the author name 531 | * @--graph@ informs git to display the commit tree in an ASCII graph layout 532 | * @--date=short@ keeps the date format nice and short 533 | 534 | p. This is a lot to type every time you want to see the log. 535 | Fortunately we will learn about git aliases in the next lab. 536 | 537 | h2. Other Tools 538 | 539 | p. Both gitx (for Macs) and gitk (any 540 | platform) are useful in exploring log history. 541 | 542 | ---------------------------------------------------------------------- 543 | h1. Aliases 544 | 545 | h2. Goals 546 | 547 | * Learn how to setup aliases and shortcuts for git commands 548 | 549 | h2. Common Aliases 550 | 551 | p. @git status@, @git add@, @git commit@, and @git checkout@ are such 552 | common commands that it is useful to have abbreviations for them. 553 | 554 | p. Add the following to the .gitconfig file in your $HOME directory. 555 | 556 | file: .gitconfig 557 | [alias] 558 | co = checkout 559 | ci = commit 560 | st = status 561 | br = branch 562 | hist = log --pretty=format:'%h %ad | %s%d [%an]' --graph --date=short 563 | type = cat-file -t 564 | dump = cat-file -p 565 | EOF 566 | 567 | p. We've covered the commit and status commands already. And we just 568 | covered the @log@ command in the previous lab. The @checkout@ command 569 | will be coming up soon. 570 | 571 | p. With these aliases defined in the @.gitconfig@ file you can type 572 | @git co@ wherever you used to have to type @git checkout@. Likewise 573 | with @git st@ for @git status@ and @git ci@ for @git commit@. And 574 | best of all, @git hist@ will allow you to avoid the really long @log@ 575 | command. 576 | 577 | p. Go ahead and give the new commands a try. 578 | 579 | h2. Define the @hist@ alias in your @.gitconfig@ file 580 | 581 | p. For the most part, I will continue to type out the full command in 582 | these instructions. The only exception is that I will use the @hist@ 583 | alias defined above anytime we need to see the git log output. Make 584 | sure you have a @hist@ alias setup in your @.gitconfig@ file before 585 | continuing if you wish to follow along. 586 | 587 | h2. @Type@ and @Dump@ 588 | 589 | p. We've added a few aliases for commands we haven't covered yet. The 590 | @git branch@ command will be coming up soon. And the @git cat-file@ 591 | command is useful for exploring git, which we will see in a little 592 | while. 593 | 594 | h2. Shell Aliases (Optional) 595 | 596 | p(note). *Note:* This section is for folks running a posix-like shell. 597 | Windows users and other non-posix shell users can feel free to skip to 598 | the next lab. 599 | 600 | p. If your shell supports aliases or shortcuts, then you can add 601 | aliases at that level too. Here are the ones I use: 602 | 603 | file: .profile 604 | alias gs='git status ' 605 | alias ga='git add ' 606 | alias gb='git branch ' 607 | alias gc='git commit' 608 | alias gd='git diff' 609 | alias gco='git checkout ' 610 | alias gk='gitk --all&' 611 | alias gx='gitx --all' 612 | 613 | alias got='git ' 614 | alias get='git ' 615 | EOF 616 | 617 | p. The @gco@ abbreviation for @git checkout@ is particularly nice. It 618 | allows me to type: 619 | 620 | pre(instructions). gco 621 | 622 | p. to checkout a particular branch. 623 | 624 | p. And yes, I do mistype @git@ as @get@ or @got@ often enough to 625 | create aliases for them. 626 | 627 | ---------------------------------------------------------------------- 628 | h1. Getting Old Versions 629 | 630 | h2. Goals 631 | 632 | * Learn how to checkout any previous snapshot into the working directory. 633 | 634 | p. Going back in history is very easy. The checkout command will copy 635 | any snapshot from the repository to the working directory. 636 | 637 | h2. Get the hashes for previous versions 638 | 639 | Execute: 640 | git hist 641 | =log 642 | 643 | p(note). *Note:* You did remember to define @hist@ in your 644 | @.gitconfig@ file, right? If not, review the lab on aliases. 645 | 646 | Output: 647 | =log 648 | EOF 649 | 650 | p. Examine the log output and find the hash for the first commit. It 651 | should be the last line of the @git hist@ output. Use that hash code 652 | (the first 7 characters are enough) in the command below. Then check 653 | the contents of the hello.rb file. 654 | 655 | Set: hash=hash_for("First Commit") 656 | Execute: 657 | git checkout 658 | =checkout 659 | cat hello.rb 660 | =cat 661 | 662 | p(note). *Note:* The commands given here are Unix commands and 663 | work on both Mac and Linux boxes. Unfortunately, Windows users will 664 | have to translate to their native commands. 665 | 666 | p(note). *Note:* Many commands depend on the hash values in the 667 | repository. Since your hash values will vary from mine, whenever you 668 | see something like <hash> or <treehash> in the command, 669 | substitute in the proper hash value for your repository. 670 | 671 | p. You should see ... 672 | 673 | Output: 674 | =checkout 675 | =cat 676 | EOF 677 | 678 | p. The output of the @checkout@ command explains the situation pretty 679 | well. Older versions of git will complain about not being on a local 680 | branch. In any case, don't worry about that for now. 681 | 682 | p. Notice the contents of the hello.rb file are the original contents. 683 | 684 | h2. Return the latest version in the main branch 685 | 686 | Execute: 687 | git checkout main 688 | =checkout2 689 | cat hello.rb 690 | =cat2 691 | 692 | p. You should see ... 693 | 694 | Output: 695 | =checkout2 696 | =cat2 697 | EOF 698 | 699 | p. 'main' is the name of the default branch. By checking out a branch 700 | by name, you go to the latest version of that branch. 701 | 702 | ---------------------------------------------------------------------- 703 | h1. Tagging versions 704 | 705 | h2. Goals 706 | 707 | * Learn how to tag commits with names for future reference 708 | 709 | p. Let's call the current version of the hello program version 1 (v1). 710 | 711 | h2. Tagging version 1 712 | 713 | Execute: 714 | git tag v1 715 | =tag 716 | 717 | p. Now you can refer to the current version of the program as v1. 718 | 719 | h2. Tagging Previous Versions 720 | 721 | p. Let's tag the version immediately prior to the current version 722 | v1-beta. First we need to checkout the previous version. Rather than 723 | look up the hash, we will use the @^@ notation to indicate "the parent 724 | of v1". 725 | 726 | p(note). If the @v1@^ notation gives you any trouble, you can also try 727 | @v1~1@, which will reference the same version. This notation means 728 | "the first ancestor of v1". 729 | 730 | Execute: 731 | git checkout v1^ 732 | =checkout 733 | cat hello.rb 734 | =cat 735 | 736 | Output: 737 | =checkout 738 | =cat 739 | EOF 740 | 741 | p. See, this is the version with the default value _before_ we added 742 | the comment. Let's make this v1-beta. 743 | 744 | Execute: 745 | git tag v1-beta 746 | 747 | h2. Checking Out by Tag Name 748 | 749 | p. Now try going back and forth between the two tagged versions. 750 | 751 | Execute: 752 | git checkout v1 753 | =cov1 754 | git checkout v1-beta 755 | =cov1beta 756 | 757 | Output: 758 | =cov1 759 | =cov1beta 760 | EOF 761 | 762 | h2. Viewing Tags using the @tag@ command 763 | 764 | p. You can see what tags are available using the @git tag@ command. 765 | 766 | Execute: 767 | git tag 768 | =tag2 769 | 770 | Output: 771 | =tag2 772 | EOF 773 | 774 | h2. Viewing Tags in the Logs 775 | 776 | p. You can also check for tags in the log. 777 | 778 | Execute: 779 | git hist main --all 780 | =hist 781 | 782 | Output: 783 | =hist 784 | EOF 785 | 786 | p. You can see both tags (@v1@ and @v1-beta@) listed in the log 787 | output, along with the branch name (@main@). Also @HEAD@ shows you 788 | the currently checked out commit (which is @v1-beta@ at the moment). 789 | 790 | ---------------------------------------------------------------------- 791 | h1. Undoing Local Changes (before staging) 792 | 793 | h2. Goals 794 | 795 | * Learn how to revert changes in the working directory 796 | 797 | h2. Checkout main 798 | 799 | p. Make sure you are on the latest commit in main before proceeding. 800 | 801 | Execute: 802 | git checkout main 803 | 804 | h2. Change hello.rb 805 | 806 | p. Sometimes you have modified a file in your local working directory 807 | and you wish to just revert to what has already been committed. The 808 | checkout command will handle that. 809 | 810 | Change hello.rb to have a bad comment. 811 | 812 | File: hello.rb 813 | # This is a bad comment. We want to revert it. 814 | name = ARGV.first || "World" 815 | 816 | puts "Hello, #{name}!" 817 | EOF 818 | 819 | h2. Check the Status 820 | 821 | p. First, check the status of the working directory. 822 | 823 | Execute: 824 | !git status 825 | =status 826 | 827 | Output: 828 | =status 829 | EOF 830 | 831 | p. We see that the @hello.rb@ file has been modified, but hasn't been 832 | staged yet. 833 | 834 | h2. Revert the changes in the working directory 835 | 836 | p. Use the @checkout@ command to checkout the repository's version of 837 | the @hello.rb@ file. 838 | 839 | Execute: 840 | git checkout hello.rb 841 | =checkout 842 | !git status 843 | =status2 844 | cat hello.rb 845 | =cat 846 | 847 | Output: 848 | =checkout 849 | =status2 850 | =cat 851 | EOF 852 | 853 | p. The status command shows us that there are no outstanding changes 854 | in the working directory. And the "bad comment" is no longer part of 855 | the file contents. 856 | 857 | ---------------------------------------------------------------------- 858 | h1. Undoing Staged Changes (before committing) 859 | 860 | h2. Goals 861 | 862 | * Learn how to revert changes that have been staged 863 | 864 | h2. Change the file and stage the change 865 | 866 | p. Modify the @hello.rb@ file to have a bad comment 867 | 868 | File: hello.rb 869 | # This is an unwanted but staged comment 870 | name = ARGV.first || "World" 871 | 872 | puts "Hello, #{name}!" 873 | EOF 874 | 875 | p. And then go ahead and stage it. 876 | 877 | Execute: 878 | git add hello.rb 879 | 880 | h2. Check the Status 881 | 882 | p. Check the status of your unwanted change. 883 | 884 | Execute: 885 | git status 886 | =status 887 | 888 | Output: 889 | =status 890 | EOF 891 | 892 | p. The status output shows that the change has been staged and is 893 | ready to be committed. 894 | 895 | h2. Reset the Staging Area 896 | 897 | p. The @reset@ command resets the staging area to be whatever is in 898 | HEAD. This clears the staging area of the change we just staged. 899 | 900 | Execute: 901 | git reset HEAD hello.rb 902 | =reset 903 | 904 | Output: 905 | =reset 906 | EOF 907 | 908 | p. The @reset@ command (by default) doesn't change the working 909 | directory. So the working directory still has the unwanted comment in 910 | it. We can use the @checkout@ command of the previous lab to remove 911 | the unwanted change from the working directory. 912 | 913 | p(note). *Note*: You could also have used the @git restore@ command to restore 914 | just the single file. 915 | 916 | h2. Checkout the Committed Version 917 | 918 | Execute: 919 | git checkout hello.rb 920 | git status 921 | =status2 922 | 923 | Output: 924 | =status2 925 | EOF 926 | 927 | p. And our working directory is clean once again. 928 | 929 | ---------------------------------------------------------------------- 930 | h1. Undoing Committed Changes 931 | 932 | h2. Goals 933 | 934 | * Learn how to revert changes that have been committed to a local repository. 935 | 936 | h2. Undoing Commits 937 | 938 | p. Sometimes you realized that a change that you have already 939 | committed was not correct and you wish to undo that commit. There are 940 | several ways of handling that issue, and the way we are going to use 941 | in this lab is always safe. 942 | 943 | p. Essentially we will undo the commit by creating a new commit that 944 | reverses the unwanted changes. 945 | 946 | h2. Change the file and commit it. 947 | 948 | p. Change the @hello.rb@ file to the following. 949 | 950 | File: hello.rb 951 | # This is an unwanted but committed change 952 | name = ARGV.first || "World" 953 | 954 | puts "Hello, #{name}!" 955 | EOF 956 | 957 | Execute: 958 | git add hello.rb 959 | git commit -m "Oops, we didn't want this commit" 960 | 961 | h2. Create a Reverting Commit 962 | 963 | p. To undo a committed change, we need to generate a commit that 964 | removes the changes introduced by our unwanted commit. 965 | 966 | Execute: 967 | -git revert HEAD 968 | +git revert HEAD --no-edit 969 | =revert 970 | 971 | p. This will pop you into the editor. You can edit the default commit 972 | message or leave it as is. Save and close the file. You should see ... 973 | 974 | Output: 975 | =revert 976 | EOF 977 | 978 | p. Since we were undoing the very last commit we made, we were able to 979 | use @HEAD@ as the argument to revert. We can revert any arbitrary 980 | commit earlier in history by simply specifying its hash value. 981 | 982 | p(note). *Note:* The @--no-edit@ in the output can be ignored. 983 | It was necessary to generate the output without opening the editor. 984 | 985 | h2. Check the log 986 | 987 | p. Checking the log shows both the unwanted and the reverting commits 988 | in our repository. 989 | 990 | Execute: 991 | git hist 992 | =hist 993 | 994 | Output: 995 | =hist 996 | EOF 997 | 998 | p. This technique will work with any commit (although you may have to 999 | resolve conflicts). It is safe to use even on branches that are 1000 | publicly shared on remote repositories. 1001 | 1002 | h2. Up Next 1003 | 1004 | p. Next, let's look at a technique that can be used to remove the most 1005 | recent commits from the repository history. 1006 | 1007 | ---------------------------------------------------------------------- 1008 | h1. Removing Commits from a Branch 1009 | 1010 | h2. Goals 1011 | 1012 | * Learn how to remove the most recent commits from a branch 1013 | 1014 | p. The @revert@ command of the previous section is a powerful command 1015 | that lets us undo the effects of any commit in the repository. 1016 | However, both the original commit and the "undoing" commit are visible 1017 | in the branch history (using the @git log@ command). 1018 | 1019 | p. Often we make a commit and immediately realize that it was a 1020 | mistake. It would be nice to have a "take back" command that would 1021 | allow us to pretend that the incorrect commit never happened. The 1022 | "take back" command would even prevent the bad commit from showing up 1023 | the @git log@ history. It would be as if the bad commit never 1024 | happened. 1025 | 1026 | h2. The @reset@ command 1027 | 1028 | p. We've already seen the @reset@ command and have used it to set the 1029 | staging area to be consistent with a given commit (we used the HEAD 1030 | commit in our previous lab). 1031 | 1032 | p. When given a commit reference (i.e. a hash, branch or tag name), 1033 | the @reset@ command will ... 1034 | 1035 | # Rewrite the current branch to point to the specified commit 1036 | # Optionally reset the staging area to match the specified commit 1037 | # Optionally reset the working directory to match the specified commit 1038 | 1039 | h2. Check Our History 1040 | 1041 | p. Let's do a quick check of our commit history. 1042 | 1043 | Execute: 1044 | git hist 1045 | =hist 1046 | 1047 | Output: 1048 | =hist 1049 | EOF 1050 | 1051 | p. We see that we have an "Oops" commit and a "Revert Oops" commit as 1052 | the last two commits made in this branch. Let's remove them using 1053 | reset. 1054 | 1055 | h2. First, Mark this Branch 1056 | 1057 | p. But before we remove the commits, let's mark the latest commit with 1058 | a tag so we can find it again. 1059 | 1060 | Execute: 1061 | git tag oops 1062 | 1063 | h2. Reset to Before Oops 1064 | 1065 | p. Looking at the log history (above), we see that the commit tagged 1066 | 'v1' is the commit right before the bad commit. Let's reset the 1067 | branch to that point. Since that branch is tagged, we can use the tag 1068 | name in the reset command (if it wasn't tagged, we could just use the 1069 | hash value). 1070 | 1071 | Execute: 1072 | git reset --hard v1 1073 | =reset 1074 | git hist 1075 | =hist2 1076 | 1077 | Output: 1078 | =reset 1079 | =hist2 1080 | EOF 1081 | 1082 | p. Our main branch now points to the v1 commit and the Oops commit 1083 | and the Revert Oops commit are no longer in the branch. The @--hard@ 1084 | parameter indicates that the working directory should be updated to be 1085 | consistent with the new branch head. 1086 | 1087 | h2. Nothing is Ever Lost 1088 | 1089 | p. But what happened to the bad commits? It turns out that the 1090 | commits are still in the repository. In fact, we can still reference 1091 | them. Remember that at the beginning of this lab we tagged the 1092 | reverting commit with the tag "oops". Let's look at _all_ the commits. 1093 | 1094 | Execute: 1095 | git hist --all 1096 | =hist3 1097 | 1098 | Output: 1099 | =hist3 1100 | EOF 1101 | 1102 | p. Here we see that the bad commits haven't disappeared. They are 1103 | still in the repository. It's just that they are no longer listed in 1104 | the main branch. If we hadn't tagged them, they would still be in 1105 | the repository, but there would be no way to reference them other than 1106 | using their hash names. Commits that are unreferenced remain in the 1107 | repository until the system runs the garbage collection software. 1108 | 1109 | h2. Dangers of Reset 1110 | 1111 | p. Resets on local branches are generally safe. Any "accidents" can 1112 | usually be recovered from by just resetting again with the desired 1113 | commit. 1114 | 1115 | p. However, if the branch is shared on remote repositories, resetting 1116 | can confuse other users sharing the branch. 1117 | 1118 | ---------------------------------------------------------------------- 1119 | h1. Remove the oops tag 1120 | 1121 | h2. Goals 1122 | 1123 | * Remove the oops tag (housekeeping) 1124 | 1125 | h2. Removing tag oops 1126 | 1127 | p. The oops tag has served its purpose. Let's remove it and allow the 1128 | commits it referenced to be garbage collected. 1129 | 1130 | Execute: 1131 | git tag -d oops 1132 | =tag 1133 | git hist --all 1134 | =hist 1135 | 1136 | Output: 1137 | =tag 1138 | =hist 1139 | EOF 1140 | 1141 | p. The oops tag is no longer listed in the repository. 1142 | 1143 | ---------------------------------------------------------------------- 1144 | h1. Amending Commits 1145 | 1146 | h2. Goals 1147 | 1148 | * Learn how to amend an existing commit 1149 | 1150 | h2. Change the program then commit 1151 | 1152 | p. Add an author comment to the program. 1153 | 1154 | File: hello.rb 1155 | # Default is World 1156 | # Author: Jim Weirich 1157 | name = ARGV.first || "World" 1158 | 1159 | puts "Hello, #{name}!" 1160 | EOF 1161 | 1162 | Execute: 1163 | git add hello.rb 1164 | git commit -m "Add an author comment" 1165 | 1166 | h2. Oops, Should have an Email 1167 | 1168 | p. After you make the commit, you realize that any good author comment 1169 | should have an email included. Update the hello program to include an 1170 | email. 1171 | 1172 | File: hello.rb 1173 | # Default is World 1174 | # Author: Jim Weirich (jim@somewhere.com) 1175 | name = ARGV.first || "World" 1176 | 1177 | puts "Hello, #{name}!" 1178 | EOF 1179 | 1180 | h2. Amend the Previous Commit 1181 | 1182 | p. We really don't want a separate commit for just the email. Let's 1183 | amend the previous commit to include the email change. 1184 | 1185 | Execute: 1186 | git add hello.rb 1187 | =add 1188 | git commit --amend -m "Add an author/email comment" 1189 | =commit 1190 | 1191 | Output: 1192 | =add 1193 | =commit 1194 | EOF 1195 | 1196 | 1197 | h2. Review the History 1198 | 1199 | Execute: 1200 | git hist 1201 | =hist 1202 | 1203 | Output: 1204 | =hist 1205 | EOF 1206 | 1207 | p. We can see the original "author" commit is now gone, and it is 1208 | replaced by the "author/email" commit. You can achieve the same 1209 | effect by resetting the branch back one commit and then recommitting 1210 | the new changes. 1211 | 1212 | ---------------------------------------------------------------------- 1213 | h1. Moving Files 1214 | 1215 | h2. Goals 1216 | 1217 | * Learn how to move a file within a repository. 1218 | 1219 | h2. Move the hello.rb file into a lib directory. 1220 | 1221 | p. We are now going to build up the structure of our little 1222 | repository. Let's move the program into a lib directory. 1223 | 1224 | Execute: 1225 | mkdir lib 1226 | =mkdir 1227 | git mv hello.rb lib 1228 | =move 1229 | !git status 1230 | =status 1231 | 1232 | Output: 1233 | =mkdir 1234 | =move 1235 | =status 1236 | EOF 1237 | 1238 | p. By using git to do the move, we inform git of 2 things 1239 | 1240 | # That the file @hello.rb@ has been deleted. 1241 | # The file @lib/hello.rb@ has been created. 1242 | 1243 | p. Both of these bits of information are immediately staged and ready 1244 | to be committed. The git status command reports that the file has been 1245 | moved. 1246 | 1247 | h2. Another way of moving files 1248 | 1249 | p. One of the nice things about git is that you can forget about 1250 | source control until the point you are ready to start committing code. 1251 | What would happen if we used the operating system command to move the 1252 | file instead of the git command? 1253 | 1254 | p. It turns out the following set of commands is identical to what we 1255 | just did. It's a bit more work, but the result is the same. 1256 | 1257 | p(command). We could have done: 1258 | 1259 | pre(instructions). mkdir lib 1260 | mv hello.rb lib 1261 | git add lib/hello.rb 1262 | git rm hello.rb 1263 | 1264 | h2. Commit the new directory 1265 | 1266 | p. Let's commit this move. 1267 | 1268 | Execute: 1269 | git commit -m "Moved hello.rb to lib" 1270 | 1271 | ---------------------------------------------------------------------- 1272 | h1. More Structure 1273 | 1274 | h2. Goals 1275 | 1276 | * Add another file to our repository 1277 | 1278 | h2. Now add a Rakefile 1279 | 1280 | p. This lab assumes you have installed *rake*. Please 1281 | do that before continuing. Check for your specific Operating System. 1282 | Otherwise execute: 1283 | 1284 | Execute: 1285 | gem install rake 1286 | 1287 | p. Let's add a Rakefile to our repository. The following one will do 1288 | nicely. 1289 | 1290 | File: Rakefile 1291 | #!/usr/bin/ruby -wKU 1292 | 1293 | task :default => :run 1294 | 1295 | task :run do 1296 | require './lib/hello' 1297 | end 1298 | EOF 1299 | 1300 | p. Add and commit the change. 1301 | 1302 | Execute: 1303 | git add Rakefile 1304 | git commit -m "Added a Rakefile." 1305 | 1306 | p. You should be able to use Rake to run your hello program now. 1307 | 1308 | Execute: 1309 | rake 1310 | =rake 1311 | 1312 | Output: 1313 | =rake 1314 | EOF 1315 | 1316 | ---------------------------------------------------------------------- 1317 | h1. Git Internals: The .git directory 1318 | 1319 | h2. Goals 1320 | 1321 | * Learn about the structure of the @.git@ directory 1322 | 1323 | h2. The @.git@ Directory 1324 | 1325 | p. Time to do some exploring. First, from the root of your project 1326 | directory... 1327 | 1328 | Execute: 1329 | ls -C .git 1330 | =lsgit 1331 | 1332 | Output: 1333 | =lsgit 1334 | EOF 1335 | 1336 | p. This is the magic directory where all the git "stuff" is stored. 1337 | Let's peek in the objects directory. 1338 | 1339 | h2. The Object Store 1340 | 1341 | Execute: 1342 | ls -C .git/objects 1343 | =lsobjs 1344 | 1345 | Output: 1346 | =lsobjs 1347 | EOF 1348 | 1349 | p. You should see a bunch of directories with 2 letter names. The 1350 | directory names are the first two letters of the sha1 hash of the 1351 | object stored in git. 1352 | 1353 | h2. Deeper into the Object Store 1354 | 1355 | Set: dir=`ls .git/objects | head -1`.strip 1356 | Execute: 1357 | ls -C .git/objects/ 1358 | =lsobjs2 1359 | 1360 | Output: 1361 | =lsobjs2 1362 | EOF 1363 | 1364 | p. Look in one of the two-letter directories. You should see some 1365 | files with 38-character names. These are the files that contain the 1366 | objects stored in git. These files are compressed and encoded, so 1367 | looking at their contents directly won't be very helpful, but we will 1368 | take a closer look in a bit. 1369 | 1370 | h2. Config File 1371 | 1372 | Execute: 1373 | cat .git/config 1374 | =cat 1375 | 1376 | Output: 1377 | =cat 1378 | EOF 1379 | 1380 | p. This is a project-specific configuration file. Config entries in 1381 | here will override the config entries in the @.gitconfig@ file in your 1382 | home directory, at least for this project. 1383 | 1384 | h2. Branches and Tags 1385 | 1386 | Execute: 1387 | ls .git/refs 1388 | =refs 1389 | ls .git/refs/heads 1390 | =refhead 1391 | ls .git/refs/tags 1392 | =reftags 1393 | cat .git/refs/tags/v1 1394 | =refv1 1395 | 1396 | Output: 1397 | =refs 1398 | =refhead 1399 | =reftags 1400 | =refv1 1401 | EOF 1402 | 1403 | p. You should recognize the files in the tags subdirectory. Each file 1404 | corresponds to a tag you created with the @git tag@ command earlier. 1405 | Its content is just the hash of the commit tied to the tag. 1406 | 1407 | p. The heads directory is similar, but is used for branches rather 1408 | than tags. We only have one branch at the moment, so all you will see 1409 | is main in this directory. 1410 | 1411 | h2. The HEAD File 1412 | 1413 | Execute: 1414 | cat .git/HEAD 1415 | =head 1416 | 1417 | Output: 1418 | =head 1419 | EOF 1420 | 1421 | p. The HEAD file contains a reference to the current branch. It 1422 | should be a reference to main at this point. 1423 | 1424 | ---------------------------------------------------------------------- 1425 | h1. Git Internals: Working directly with Git Objects 1426 | 1427 | h2. Goals 1428 | 1429 | * Explore the structure of the object store 1430 | * Learn how to use the SHA1 hashes to find content in the repository 1431 | 1432 | p. Now let's use some tools to probe git objects directly. 1433 | 1434 | h2. Finding the Latest Commit 1435 | 1436 | Execute: 1437 | git hist --max-count=1 1438 | =log 1439 | 1440 | p. This should show the latest commit made in the repository. The 1441 | SHA1 hash on your system is probably different from what is on mine, 1442 | but you should see something like this. 1443 | 1444 | Output: 1445 | =log 1446 | EOF 1447 | 1448 | h2. Dumping the Latest Commit 1449 | 1450 | Using the SHA1 hash from the commit listed above ... 1451 | 1452 | Set: hash=hash_for("Added a Rakefile") 1453 | Execute: 1454 | git cat-file -t 1455 | =type 1456 | git cat-file -p 1457 | =dump 1458 | 1459 | Here's my output ... 1460 | 1461 | Output: 1462 | =type 1463 | =dump 1464 | EOF 1465 | 1466 | p(note). *NOTE:* If you defined the 'type' and 'dump' aliases from 1467 | the aliases lab, then you can type @git type@ and @git dump@ rather 1468 | than the longer cat-file commands (which I never remember). 1469 | 1470 | p. This is the dump of the commit object that is at the head of the 1471 | main branch. It looks a lot like the commit object from the 1472 | presentation earlier. 1473 | 1474 | h2. Finding the Tree 1475 | 1476 | p. We can dump the directory tree referenced in the commit. This 1477 | should be a description of the (top level) files in our project (for 1478 | that commit). Use the SHA1 hash from the "tree" line listed above. 1479 | 1480 | Set: treehash=hash_in(var['hash'], 'tree') 1481 | Execute: 1482 | git cat-file -p 1483 | =treedump 1484 | 1485 | p. Here's what my tree looks like... 1486 | 1487 | Output: 1488 | =treedump 1489 | EOF 1490 | 1491 | p. Yep, I see the Rakefile and the lib directory. 1492 | 1493 | h2. Dumping the lib directory 1494 | 1495 | Set: libhash=hash_in(var['treehash'], 'lib') 1496 | Execute: 1497 | git cat-file -p 1498 | =libdump 1499 | 1500 | Output: 1501 | =libdump 1502 | EOF 1503 | 1504 | p. There's the @hello.rb@ file. 1505 | 1506 | h2. Dumping the @hello.rb@ file 1507 | 1508 | Set: rbhash=hash_in(var['libhash'], 'hello') 1509 | Execute: 1510 | git cat-file -p 1511 | =rbdump 1512 | 1513 | Output: 1514 | =rbdump 1515 | EOF 1516 | 1517 | p. There you have it. We've dumped commit objects, tree objects and 1518 | blob objects directly from the git repository. That's all there is to 1519 | it, blobs, trees and commits. 1520 | 1521 | h2. Explore On You Own 1522 | 1523 | p. Explore the git repo manually on your own. See if you can find the 1524 | original hello.rb file from the very first commit by manually 1525 | following the SHA1 hash references starting in the latest commit. 1526 | 1527 | ---------------------------------------------------------------------- 1528 | h1. Creating a Branch 1529 | 1530 | h2. Goals 1531 | 1532 | * Learn how to create a local branch in a repository 1533 | 1534 | p. It's time to do a major rewrite of the hello world functionality. 1535 | Since this might take awhile, you'll want to put these changes into a 1536 | separate branch to isolate them from changes in main. 1537 | 1538 | h2. Create a Branch 1539 | 1540 | p. Let's call our new branch 'greet'. 1541 | 1542 | Execute: 1543 | git checkout -b greet 1544 | !git status 1545 | 1546 | p(note). *NOTE:* @git checkout -b @ is a shortcut for @git 1547 | branch @ followed by a @git checkout @. 1548 | 1549 | p. Notice that the git status command reports that you are on the 1550 | 'greet' branch. 1551 | 1552 | h2. Changes for Greet: Add a Greeter class. 1553 | 1554 | File: lib/greeter.rb 1555 | class Greeter 1556 | def initialize(who) 1557 | @who = who 1558 | end 1559 | def greet 1560 | "Hello, #{@who}" 1561 | end 1562 | end 1563 | EOF 1564 | 1565 | Execute: 1566 | git add lib/greeter.rb 1567 | git commit -m "Added greeter class" 1568 | 1569 | h2. Changes for Greet: Modify the main program 1570 | 1571 | p. Update the hello.rb file to use greeter 1572 | 1573 | File: lib/hello.rb 1574 | require 'greeter' 1575 | 1576 | # Default is World 1577 | name = ARGV.first || "World" 1578 | 1579 | greeter = Greeter.new(name) 1580 | puts greeter.greet 1581 | EOF 1582 | 1583 | Execute: 1584 | git add lib/hello.rb 1585 | git commit -m "Hello uses Greeter" 1586 | 1587 | h2. Changes for Greet: Update the Rakefile 1588 | 1589 | p. Update the Rakefile to use an external ruby process 1590 | 1591 | File: Rakefile 1592 | #!/usr/bin/ruby -wKU 1593 | 1594 | task :default => :run 1595 | 1596 | task :run do 1597 | ruby '-Ilib', 'lib/hello.rb' 1598 | end 1599 | EOF 1600 | 1601 | Execute: 1602 | git add Rakefile 1603 | git commit -m "Updated Rakefile" 1604 | 1605 | h2. Up Next 1606 | 1607 | p. We now have a new branch called *greet* with 3 new commits on it. 1608 | Next we will learn how to navigate and switch between branches. 1609 | 1610 | ---------------------------------------------------------------------- 1611 | h1. Navigating Branches 1612 | 1613 | h2. Goals 1614 | 1615 | * Learn how to navigate between the branches of a repository 1616 | 1617 | p. You now have two branches in your project: 1618 | 1619 | Execute: 1620 | git hist --all 1621 | =log 1622 | 1623 | Output: 1624 | =log 1625 | EOF 1626 | 1627 | h2. Switch to the Main Branch 1628 | 1629 | p. Just use the @git checkout@ command to switch between branches. 1630 | 1631 | Execute: 1632 | git checkout main 1633 | =checkout 1634 | cat lib/hello.rb 1635 | =cat 1636 | 1637 | Output: 1638 | =checkout 1639 | =cat 1640 | EOF 1641 | 1642 | p. You are now on the main branch. You can tell because the 1643 | hello.rb file doesn't use the @Greeter@ class. 1644 | 1645 | h2. Switch Back to the Greet Branch. 1646 | 1647 | Execute: 1648 | git checkout greet 1649 | =checkout2 1650 | cat lib/hello.rb 1651 | =cat2 1652 | 1653 | Output: 1654 | =checkout2 1655 | =cat2 1656 | EOF 1657 | 1658 | p. The contents of the @lib/hello.rb@ confirms we are back on the 1659 | *greet* branch. 1660 | 1661 | ---------------------------------------------------------------------- 1662 | h1. Changes in Main 1663 | 1664 | h2. Goals 1665 | 1666 | * Learning how to deal with multiple branches with different (and possibly conflicting) changes. 1667 | 1668 | p. While you were changing the greet branch, someone else decided to 1669 | update the main branch. They added a README. 1670 | 1671 | h2. Switch to the main branch. 1672 | 1673 | Execute: 1674 | git checkout main 1675 | 1676 | h2. Create the README. 1677 | 1678 | File: README 1679 | This is the Hello World example from the git tutorial. 1680 | EOF 1681 | 1682 | h2. Commit the README to main. 1683 | 1684 | Execute: 1685 | git add README 1686 | git commit -m "Added README" 1687 | 1688 | ---------------------------------------------------------------------- 1689 | h1. Viewing Diverging Branches 1690 | 1691 | h2. Goals 1692 | 1693 | * Learn how to view diverging branches in a repository. 1694 | 1695 | h2. View the Current Branches 1696 | 1697 | p. We now have two diverging branches in the repository. Use the 1698 | following log command to view the branches and how they diverge. 1699 | 1700 | Execute: 1701 | git hist --all 1702 | =log 1703 | 1704 | Output: 1705 | =log 1706 | EOF 1707 | 1708 | p. Here is our first chance to see the @--graph@ option on @git hist@ in 1709 | action. Adding the @--graph@ option to @git log@ causes it to draw the 1710 | commit tree using simple ASCII characters. We can see both branches 1711 | (greet and main), and that the main branch is the current HEAD. 1712 | The common ancestor to both branches is the "Added a Rakefile" branch. 1713 | 1714 | p. The @--all@ flag makes sure that we see all the branches. The 1715 | default is to show only the current branch. 1716 | 1717 | ---------------------------------------------------------------------- 1718 | h1. Merging 1719 | 1720 | h2. Goals 1721 | 1722 | * Learn how to merge two diverging branches to bring the changes back into a single branch. 1723 | 1724 | h2. Merge the branches 1725 | 1726 | p. Merging brings the changes in two branches together. Let's go back 1727 | to the greet branch and merge main onto greet. 1728 | 1729 | Execute: 1730 | git checkout greet 1731 | =checkout 1732 | git merge main 1733 | =merge 1734 | git hist --all 1735 | =hist 1736 | 1737 | Output: 1738 | =checkout 1739 | =merge 1740 | =hist 1741 | EOF 1742 | 1743 | p. By merging main into your greet branch periodically, you can pick 1744 | up any changes to main and keep your changes in greet compatible 1745 | with changes in the mainline. 1746 | 1747 | p. However, it does produce ugly commit graphs. Later we will look at 1748 | the option of rebasing rather than merging. 1749 | 1750 | h2. Up Next 1751 | 1752 | p. But first, what if the changes in main conflict with the changes 1753 | in greet? 1754 | 1755 | ---------------------------------------------------------------------- 1756 | h1. Creating a Conflict 1757 | 1758 | h2. Goals 1759 | 1760 | * Create a conflicting change in the main branch. 1761 | 1762 | h2. Switch back to main and create a conflict 1763 | 1764 | p. Switch back to the main branch and make this change: 1765 | 1766 | Execute: 1767 | git checkout main 1768 | 1769 | File: lib/hello.rb 1770 | puts "What's your name" 1771 | my_name = gets.strip 1772 | 1773 | puts "Hello, #{my_name}!" 1774 | EOF 1775 | 1776 | Execute: 1777 | git add lib/hello.rb 1778 | git commit -m "Made interactive" 1779 | 1780 | h2. View the Branches 1781 | 1782 | Execute: 1783 | git hist --all 1784 | =log 1785 | 1786 | Output: 1787 | =log 1788 | EOF 1789 | 1790 | p. main at commit "Added README" has been merged to the greet 1791 | branch, but there is now an additional commit on main that has not 1792 | been merged back to greet. 1793 | 1794 | h2. Up Next 1795 | 1796 | p. The latest change in main conflicts with some existing changes in 1797 | greet. Next we will resolve those changes. 1798 | 1799 | ---------------------------------------------------------------------- 1800 | h1. Resolving Conflicts 1801 | 1802 | h2. Goals 1803 | 1804 | * Learn how to handle conflicts during a merge 1805 | 1806 | h2. Merge main to greet 1807 | 1808 | p. Now go back to the greet branch and try to merge the new main. 1809 | 1810 | Execute: 1811 | git checkout greet 1812 | !git merge main 1813 | 1814 | Output: 1815 | $ git checkout greet 1816 | Switched to branch 'greet' 1817 | $ git merge main 1818 | Auto-merging lib/hello.rb 1819 | CONFLICT (content): Merge conflict in lib/hello.rb 1820 | Automatic merge failed; fix conflicts and then commit the result. 1821 | EOF 1822 | 1823 | If you open lib/hello.rb, you will see: 1824 | 1825 | file: lib/hello.rb 1826 | <<<<<<< HEAD 1827 | require 'greeter' 1828 | 1829 | # Default is World 1830 | name = ARGV.first || "World" 1831 | 1832 | greeter = Greeter.new(name) 1833 | puts greeter.greet 1834 | ======= 1835 | # Default is World 1836 | 1837 | puts "What's your name" 1838 | my_name = gets.strip 1839 | 1840 | puts "Hello, #{my_name}!" 1841 | >>>>>>> main 1842 | EOF 1843 | 1844 | p. The first section is the version on the head of the current branch 1845 | (greet). The second section is the version on the main branch. 1846 | 1847 | h2. Fix the Conflict 1848 | 1849 | p. You need to manually resolve the conflict. Modify @lib/hello.rb@ 1850 | to be the following. 1851 | 1852 | File: lib/hello.rb 1853 | require 'greeter' 1854 | 1855 | puts "What's your name" 1856 | my_name = gets.strip 1857 | 1858 | greeter = Greeter.new(my_name) 1859 | puts greeter.greet 1860 | EOF 1861 | 1862 | h2. Commit the Conflict Resolution 1863 | 1864 | Execute: 1865 | git add lib/hello.rb 1866 | =add 1867 | git commit -m "Merged main fixed conflict." 1868 | =commit 1869 | 1870 | Output: 1871 | =add 1872 | =commit 1873 | EOF 1874 | 1875 | h2. Advanced Merging 1876 | 1877 | p. git doesn't provide any graphical merge tools, but it will gladly 1878 | work with any third party merge tool you wish to use. See 1879 | "https://git-scm.com/book/en/v2/Customizing-Git-Git-Configuration#_external_merge_tools":https://git-scm.com/book/en/v2/Customizing-Git-Git-Configuration#_external_merge_tools 1880 | for a description of using the Perforce merge tool with git. 1881 | 1882 | ---------------------------------------------------------------------- 1883 | h1. Rebasing VS Merging 1884 | 1885 | h2. Goals 1886 | 1887 | * Learn the differences between rebasing and merging. 1888 | 1889 | h2. Discussion 1890 | 1891 | p. Let's explore the differences between merging and rebasing. In 1892 | order to do so, we need to rewind the repository back in time before 1893 | the first merge, and then redo the same steps, but using rebasing 1894 | rather than merging. 1895 | 1896 | p. We will make use the of the @reset@ command to wind the branches 1897 | back in time. 1898 | 1899 | ---------------------------------------------------------------------- 1900 | h1. Resetting the Greet Branch 1901 | 1902 | h2. Goals 1903 | 1904 | * Reset the greet branch to the point before the first merge. 1905 | 1906 | h2. Reset the greet branch 1907 | 1908 | p. Let's go back in time on the greet branch to the point _before_ we 1909 | merged main onto it. We can *reset* a branch to any commit we want. 1910 | Essentially this is modifying the branch pointer to point to anywhere 1911 | in the commit tree. 1912 | 1913 | p. In this case we want to back greet up to the point prior to the 1914 | merge with main. We need to find the last commit before the merge. 1915 | 1916 | Execute: 1917 | git checkout greet 1918 | =checkout 1919 | git hist 1920 | =log 1921 | 1922 | Output: 1923 | =checkout 1924 | =log 1925 | EOF 1926 | 1927 | p. That's a bit hard to read, but looking at the data we see that the 1928 | "Updated Rakefile" commit was the last commit on the greet branch 1929 | before merging. Let's reset the greet branch to that commit. 1930 | 1931 | Set: hash=hash_for("Updated Rakefile") 1932 | Execute: 1933 | git reset --hard 1934 | =reset 1935 | 1936 | Output: 1937 | =reset 1938 | EOF 1939 | 1940 | h2. Check the branch. 1941 | 1942 | p. Look at the log for the greet branch. We no longer have the merge 1943 | commits in its history. 1944 | 1945 | Execute: 1946 | git hist --all 1947 | =log2 1948 | 1949 | Output: 1950 | =log2 1951 | EOF 1952 | 1953 | ---------------------------------------------------------------------- 1954 | h1. Resetting the Main Branch 1955 | 1956 | h2. Goals 1957 | 1958 | * Reset the main branch to the point before the conflicting commit. 1959 | 1960 | h2. Reset the main branch 1961 | 1962 | p. When we added the interactive mode to the main branch, we made a 1963 | change that conflicted with changes in the greet branch. Let's rewind 1964 | the main branch to a point before the conflicting change. This 1965 | allows us to demonstrate the rebase command without worrying about 1966 | conflicts. 1967 | 1968 | Execute: 1969 | git checkout main 1970 | git hist 1971 | =log 1972 | 1973 | Output: 1974 | =log 1975 | EOF 1976 | 1977 | p. The 'Added README' commit is the one directly before the 1978 | conflicting interactive mode. We will reset the main branch to 1979 | 'Added README' commit. 1980 | 1981 | Set: hash=hash_for("Added README") 1982 | Execute: 1983 | git reset --hard 1984 | git hist --all 1985 | =log2 1986 | 1987 | p. Review the log. It should look like the repository has been wound 1988 | back in time to the point before we merged anything. 1989 | 1990 | Output: 1991 | =log2 1992 | EOF 1993 | 1994 | ---------------------------------------------------------------------- 1995 | h1. Rebasing 1996 | 1997 | h2. Goals 1998 | 1999 | * Use the rebase command rather than the merge command. 2000 | 2001 | p. Ok, we are back in time before the first merge and we want to get 2002 | the changes in main into our greet branch. 2003 | 2004 | p. This time we will use the rebase command instead of the merge 2005 | command to bring in the changes from the main branch. 2006 | 2007 | Execute: 2008 | git checkout greet 2009 | git rebase main 2010 | git hist 2011 | =log 2012 | 2013 | Output: 2014 | $ git checkout greet 2015 | Switched to branch 'greet' 2016 | $ 2017 | $ git rebase main 2018 | First, rewinding head to replay your work on top of it... 2019 | Applying: added Greeter class 2020 | Applying: hello uses Greeter 2021 | Applying: updated Rakefile 2022 | $ 2023 | =log 2024 | EOF 2025 | 2026 | h2. Merge VS Rebase 2027 | 2028 | p. The final result of the rebase is very similar to the merge. The 2029 | greet branch now contains all of its changes, as well as all the 2030 | changes from the main branch. However, the commit tree is quite 2031 | different. The commit tree for the greet branch has been rewritten so 2032 | that the main branch is a part of the commit history. This leaves 2033 | the chain of commits linear and much easier to read. 2034 | 2035 | h2. When to Rebase, When to Merge? 2036 | 2037 | p. Don't use rebase ... 2038 | 2039 | # If the branch is public and shared with others. Rewriting publicly shared branches will tend to screw up other members of the team. 2040 | # When the _exact_ history of the commit branch is important (since rebase rewrites the commit history). 2041 | 2042 | p. Given the above guidelines, I tend to use rebase for short-lived, 2043 | local branches and merge for branches in the public repository. 2044 | 2045 | ---------------------------------------------------------------------- 2046 | h1. Merging Back to Main 2047 | 2048 | h2. Goals 2049 | 2050 | * We've kept our greet branch up to date with main (via rebase), now let's merge the greet changes back into the main branch. 2051 | 2052 | h2. Merge greet into main 2053 | 2054 | Execute: 2055 | git checkout main 2056 | git merge greet 2057 | =merge 2058 | 2059 | Output: 2060 | $ git checkout main 2061 | Switched to branch 'main' 2062 | $ 2063 | =merge 2064 | EOF 2065 | 2066 | p. Because the head of main is a direct ancestor of the head of the 2067 | greet branch, git is able to do a fast-forward merge. When 2068 | fast-forwarding, the branch pointer is simply moved forward to point 2069 | to the same commit as the greeter branch. 2070 | 2071 | p. There will never be conflicts in a fast-forward merge. 2072 | 2073 | h2. Review the logs 2074 | 2075 | Execute: 2076 | git hist 2077 | =log 2078 | 2079 | Output: 2080 | =log 2081 | EOF 2082 | 2083 | p. The greet and main branches are now identical. 2084 | 2085 | ---------------------------------------------------------------------- 2086 | h1. Multiple Repositories 2087 | 2088 | p. Up to this point we have been working with a single git repository. 2089 | However, git excels at working with multiple repositories. These 2090 | extra repositories may be stored locally, or may be accessed across a 2091 | network connection. 2092 | 2093 | p. In the next section we will create a new repository called 2094 | "cloned_hello". We will show how to move changes from one repository 2095 | to another, and how to handle conflicts when they arise between 2096 | two repositories. 2097 | 2098 | !git_clone.png! 2099 | 2100 | 2101 | p. For now, we will be working with local repositories 2102 | (i.e. repositories stored on your local hard disk), however most of 2103 | the things learned in this section will apply to multiple repositories 2104 | whether they are stored locally or remotely over a network. 2105 | 2106 | p. *NOTE:* We are going be making changes to both copies of our 2107 | repositories. Make sure you pay attention to which repository you are 2108 | in at each step of the following labs. 2109 | 2110 | ---------------------------------------------------------------------- 2111 | h1. Cloning Repositories 2112 | 2113 | h2. Goals 2114 | 2115 | * Learn how to make copies of repositories. 2116 | 2117 | h2. Go to the work directory 2118 | 2119 | p. Go to the working directory and make a clone of your hello 2120 | repository. 2121 | 2122 | Execute: 2123 | cd .. 2124 | =cd 2125 | pwd 2126 | =pwd 2127 | ls 2128 | =ls 2129 | 2130 | p{color:red}. *NOTE: Now in the work directory.* 2131 | 2132 | Output: 2133 | =cd 2134 | =pwd 2135 | =ls 2136 | EOF 2137 | 2138 | p. At this point you should be in your "work" directory. There should 2139 | be a single repository here named "hello". 2140 | 2141 | h2. Create a clone of the hello repository 2142 | 2143 | p. Let's make a clone of the repository. 2144 | 2145 | Execute: 2146 | git clone hello cloned_hello 2147 | =clone 2148 | ls 2149 | =ls2 2150 | +cd cloned_hello 2151 | +git config user.name "Jim Weirich" 2152 | +git config user.email "jim (at) edgecase.com" 2153 | +cd .. 2154 | 2155 | Output: 2156 | =clone 2157 | =ls2 2158 | EOF 2159 | 2160 | p. There should now be two repositories in your work directory: the 2161 | original "hello" repository and the newly cloned "cloned_hello" 2162 | repository. 2163 | 2164 | ---------------------------------------------------------------------- 2165 | h1. Review the Cloned Repository 2166 | 2167 | h2. Goals 2168 | 2169 | * Learn about branches on remote repositories. 2170 | 2171 | h2. Look at the cloned repository 2172 | 2173 | p. Let's take a look at the cloned repository. 2174 | 2175 | Execute: 2176 | cd cloned_hello 2177 | =cd 2178 | ls 2179 | =ls 2180 | 2181 | Output: 2182 | =cd 2183 | =ls 2184 | EOF 2185 | 2186 | p. You should see a list of all the files in the top level of the 2187 | original repository (@README@, @Rakefile@ and @lib@). 2188 | 2189 | 2190 | h2. Review the Repository History 2191 | 2192 | Execute: 2193 | git hist --all 2194 | =log 2195 | 2196 | Output: 2197 | =log 2198 | EOF 2199 | 2200 | p. You should now see a list of all the commits in the new 2201 | repository, and it should (more or less) match the history of commits 2202 | in the original repository. The only difference should be in the 2203 | names of the branches. 2204 | 2205 | h2. Remote branches 2206 | 2207 | p. You should see a *main* branch (along with *HEAD*) in the history 2208 | list. But you will also have a number of strangely named branches 2209 | (*origin/main*, *origin/greet* and *origin/HEAD*). We'll talk about 2210 | them in a bit. 2211 | 2212 | ---------------------------------------------------------------------- 2213 | h1. What is Origin? 2214 | 2215 | h2. Goals 2216 | 2217 | * Learn about naming remote repositories. 2218 | 2219 | Execute: 2220 | git remote 2221 | =remote 2222 | 2223 | Output: 2224 | =remote 2225 | EOF 2226 | 2227 | p. We see that the cloned repository knows about a remote repository 2228 | named origin. Let's see if we can get more information about origin: 2229 | 2230 | Execute: 2231 | git remote show origin 2232 | =show 2233 | 2234 | Output: 2235 | =show 2236 | EOF 2237 | 2238 | p. Now we see that the remote repository "origin" is simply the original 2239 | *hello* repository. Remote repositories typically live on a separate 2240 | machine, possibly a centralized server. As we can see here, however, 2241 | they can just as well point to a repository on the same machine. 2242 | There is nothing particularly special about the name "origin", 2243 | however the convention is to use the name "origin" for 2244 | the primary centralized repository (if there is one). 2245 | 2246 | ---------------------------------------------------------------------- 2247 | h1. Remote Branches 2248 | 2249 | h2. Goals 2250 | 2251 | * Learn about local VS remote branches 2252 | 2253 | p. Let's look at the branches available in our cloned repository. 2254 | 2255 | Execute: 2256 | git branch 2257 | =branch 2258 | 2259 | Output: 2260 | =branch 2261 | EOF 2262 | 2263 | p. That's it, only the main branch is listed. Where is the greet 2264 | branch? The *git* *branch* command only lists the local branches by 2265 | default. 2266 | 2267 | h2. List Remote Branches 2268 | 2269 | p. Try this to see all the branches: 2270 | 2271 | Execute: 2272 | git branch -a 2273 | =branch_a 2274 | 2275 | Output: 2276 | =branch_a 2277 | EOF 2278 | 2279 | p. Git has all the commits from the original repository, but branches 2280 | in the remote repository are not treated as local branches here. If 2281 | we want our own *greet* branch, we need to create it ourselves. We 2282 | will see how to do that in a minute. 2283 | 2284 | ---------------------------------------------------------------------- 2285 | h1. Change the Original Repository 2286 | 2287 | h2. Goals 2288 | 2289 | * Make some changes to the original repository so we can try to pull the changes 2290 | 2291 | h2. Make a change in the original *hello* repository 2292 | 2293 | Execute: 2294 | cd ../hello 2295 | +pwd 2296 | # (You should be in the original hello repository now) 2297 | 2298 | p{color:red}. *NOTE: Now in the _hello_ repo* 2299 | 2300 | p. Make the following changes to README: 2301 | 2302 | File: README 2303 | This is the Hello World example from the git tutorial. 2304 | (changed in original) 2305 | EOF 2306 | 2307 | p. Now add and commit this change 2308 | 2309 | Execute: 2310 | git add README 2311 | git commit -m "Changed README in original repo" 2312 | 2313 | h2. Up Next 2314 | 2315 | p. The original repository now has later changes that are not in the 2316 | cloned version. Next we will pull those changes across to the cloned 2317 | repository. 2318 | 2319 | ---------------------------------------------------------------------- 2320 | h1. Fetching Changes 2321 | 2322 | h2. Goals 2323 | 2324 | * Learn how to pull changes from a remote repository. 2325 | 2326 | Execute: 2327 | cd ../cloned_hello 2328 | +pwd 2329 | git fetch 2330 | =fetch 2331 | git hist --all 2332 | =hist 2333 | 2334 | p(note red). *NOTE: Now in the _cloned_hello_ repo* 2335 | 2336 | Output: 2337 | =fetch 2338 | =hist 2339 | EOF 2340 | 2341 | p. At this point the repository has all the commits from the original 2342 | repository, but they are not integrated into the cloned 2343 | repository's local branches. 2344 | 2345 | p. Find the "Changed README in original repo" commit in the history 2346 | above. Notice that the commit includes "origin/main" and 2347 | "origin/HEAD". 2348 | 2349 | p. Now look at the "Updated Rakefile" commit. You will see that 2350 | the local main branch points to this commit, not to the new commit 2351 | that we just fetched. 2352 | 2353 | p. The upshot of this is that the "git fetch" command will fetch new 2354 | commits from the remote repository, but it will not merge these 2355 | commits into the local branches. 2356 | 2357 | h2. Check the README 2358 | 2359 | We can demonstrate that the cloned README is unchanged. 2360 | 2361 | Execute: 2362 | cat README 2363 | 2364 | Output: 2365 | $ cat README 2366 | This is the Hello World example from the git tutorial. 2367 | EOF 2368 | 2369 | p. See, no changes. 2370 | 2371 | ---------------------------------------------------------------------- 2372 | h1. Merging Pulled Changes 2373 | 2374 | h2. Goals 2375 | 2376 | * Learn to get the pulled changes into the current branch and working directory. 2377 | 2378 | h2. Merge the fetched changes into local main 2379 | 2380 | Execute: 2381 | git merge origin/main 2382 | =merge 2383 | 2384 | Output: 2385 | =merge 2386 | EOF 2387 | 2388 | h2. Check the README again 2389 | 2390 | p. We should see the changes now. 2391 | 2392 | Execute: 2393 | cat README 2394 | 2395 | Output: 2396 | $ cat README 2397 | This is the Hello World example from the git tutorial. 2398 | (changed in original) 2399 | EOF 2400 | 2401 | p. There are the changes. Even though "git fetch" does not merge the 2402 | changes, we can still manually merge the changes from the remote 2403 | repository. 2404 | 2405 | h2. Up Next 2406 | 2407 | p. Next let's take a look at combining the fetch & merge process into 2408 | a single command. 2409 | 2410 | ---------------------------------------------------------------------- 2411 | h1. Pulling Changes 2412 | 2413 | h2. Goals 2414 | 2415 | * Learn that @git pull@ is equivalent to a @git fetch@ followed by a @git merge@. 2416 | 2417 | h2. Discussion 2418 | 2419 | p. We're not going to go through the process of creating another 2420 | change and pulling it again, but we do want you to know that doing: 2421 | 2422 | pre(instructions). git pull 2423 | 2424 | is indeed equivalent to the two steps: 2425 | 2426 | pre(instructions). git fetch 2427 | git merge origin/main 2428 | 2429 | ---------------------------------------------------------------------- 2430 | h1. Adding a Tracking Branch 2431 | 2432 | h2. Goals 2433 | 2434 | * Learn how to add a local branch that tracks a remote branch. 2435 | 2436 | p. The branches starting with remotes/origin are branches from the 2437 | original repo. Notice that you don't have a branch called greet 2438 | anymore, but it knows that the original repo had a greet branch. 2439 | 2440 | h2. Add a local branch that tracks a remote branch. 2441 | 2442 | Execute: 2443 | git branch --track greet origin/greet 2444 | =branch_track 2445 | git branch -a 2446 | =branch_a 2447 | git hist --max-count=2 2448 | =log 2449 | 2450 | Output: 2451 | =branch_track 2452 | =branch_a 2453 | =log 2454 | EOF 2455 | 2456 | p. We can now see the greet branch in the branch list and in the log. 2457 | 2458 | ---------------------------------------------------------------------- 2459 | h1. Bare Repositories 2460 | 2461 | h2. Goals 2462 | 2463 | * Learn how to create bare repositories. 2464 | 2465 | p. Bare repositories (without working directories) are usually used for 2466 | sharing. 2467 | 2468 | h2. Create a bare repository. 2469 | 2470 | Execute: 2471 | cd .. 2472 | +pwd 2473 | git clone --bare hello hello.git 2474 | =clone 2475 | ls hello.git 2476 | =ls 2477 | 2478 | p{color:red}. *NOTE: Now in the work directory* 2479 | 2480 | Output: 2481 | =clone 2482 | =ls 2483 | EOF 2484 | 2485 | p. The convention is that repositories ending in '.git' are bare 2486 | repositories. We can see that there is no working directory in the 2487 | hello.git repo. Essentially it is nothing but the .git directory of a 2488 | non-bare repo. 2489 | 2490 | ---------------------------------------------------------------------- 2491 | h1. Adding a Remote Repository 2492 | 2493 | h2. Goals 2494 | 2495 | * Add the bare repository as a remote to our original repository. 2496 | 2497 | p. Let's add the hello.git repo to our original repo. 2498 | 2499 | Execute: 2500 | cd hello 2501 | +pwd 2502 | git remote add shared ../hello.git 2503 | 2504 | p{color:red}. *NOTE: Now in the +hello+ repository.* 2505 | 2506 | ---------------------------------------------------------------------- 2507 | h1. Pushing a Change 2508 | 2509 | h2. Goals 2510 | 2511 | * Learn how to push a change to a remote repository. 2512 | 2513 | p. Since bare repositories are usually shared on some sort of network 2514 | server, it is usually difficult to cd into the repo and pull changes. 2515 | So we need to push our changes into other repositories. 2516 | 2517 | p. Let's start by creating a change to be pushed. Edit the README and 2518 | commit it 2519 | 2520 | File: README 2521 | This is the Hello World example from the git tutorial. 2522 | (Changed in the original and pushed to shared) 2523 | EOF 2524 | 2525 | Execute: 2526 | git checkout main 2527 | git add README 2528 | git commit -m "Added shared comment to readme" 2529 | 2530 | p. Now push the change to the shared repo. 2531 | 2532 | Execute: 2533 | git push shared main 2534 | =push 2535 | 2536 | p. _shared_ is the name of the repository receiving the changes we are 2537 | pushing. (Remember, we added it as a remote in the previous lab.) 2538 | 2539 | Output: 2540 | =push 2541 | EOF 2542 | 2543 | p(note). *NOTE:* We had to explicitly name the branch main that was 2544 | receiving the push. It is possible to set it up automatically, but I 2545 | _never_ remember the commands to do that. Check out the "Git Remote 2546 | Branch" gem for easy management of remote branches. 2547 | 2548 | ---------------------------------------------------------------------- 2549 | h1. Pulling Shared Changes 2550 | 2551 | h2. Goals 2552 | 2553 | * Learn how to pull changes from a shared repository. 2554 | 2555 | p. Quick hop over to the clone repository and let's pull down the 2556 | changes just pushed to the shared repo. 2557 | 2558 | Execute: 2559 | cd ../cloned_hello 2560 | +pwd 2561 | 2562 | p{color:red}. *NOTE: Now in the _cloned_hello_ repo.* 2563 | 2564 | p. Continue with... 2565 | 2566 | Execute: 2567 | git remote add shared ../hello.git 2568 | git branch --track shared main 2569 | git pull shared main 2570 | cat README 2571 | 2572 | ---------------------------------------------------------------------- 2573 | h1. Hosting your Git Repositories 2574 | 2575 | h2. Goals 2576 | 2577 | * Learn how to setup git server for sharing repositories. 2578 | 2579 | p. There are many ways to share git repositories over the network. 2580 | Here is a quick and dirty way. 2581 | 2582 | h2. Start up the git server 2583 | 2584 | execute: 2585 | # (From the work directory) 2586 | git daemon --verbose --export-all --base-path=. 2587 | 2588 | p. Now, in a separate terminal window, go to your work directory 2589 | 2590 | execute: 2591 | # (From the work directory) 2592 | git clone git://localhost/hello.git network_hello 2593 | cd network_hello 2594 | ls 2595 | 2596 | p. You should see a copy of hello project. 2597 | 2598 | h2. Pushing to the Git Daemon 2599 | 2600 | p. If you want to push to the git daemon repository, add 2601 | @--enable=receive-pack@ to the git daemon command. Be careful because 2602 | there is no authentication on this server, anyone could push to your 2603 | repository. 2604 | 2605 | ---------------------------------------------------------------------- 2606 | h1. Sharing Repos 2607 | 2608 | h2. Goals 2609 | 2610 | * Learn to share repos across WIFI. 2611 | 2612 | p. See if your neighbor is running the git daemon. Exchange IP 2613 | addresses and see if you can pull from each other's repositories. 2614 | 2615 | p(note). *NOTE:* The gitjour gem is really useful in sharing 2616 | ad-hoc repositories. 2617 | 2618 | ---------------------------------------------------------------------- 2619 | h1. Advanced / Future Topics 2620 | 2621 | p. Here are some topics you might want to research on your own: 2622 | 2623 | * Protocols 2624 | * SSH Setup 2625 | * Remote Branch Management 2626 | * Finding Buggy Commits (git bisect) 2627 | * Workflows 2628 | * Non-command line tools (gitx, gitk, magit) 2629 | * Working with GitHub 2630 | 2631 | ---------------------------------------------------------------------- 2632 | h1. Thank You 2633 | 2634 | p. Thank you for trying out the Git Immersion Labs. Feel free to send 2635 | comments to jim.weirich@gmail.com. 2636 | 2637 | ---------------------------------------------------------------------- 2638 | -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "GitImmersion", 3 | "name": "Git Immersion", 4 | "background_color": "#ffffff", 5 | "theme_color": "#0000ff", 6 | "display": "standalone", 7 | "start_url": "/" 8 | } 9 | -------------------------------------------------------------------------------- /src/reset.css: -------------------------------------------------------------------------------- 1 | html { box-sizing: border-box; } 2 | *, *:before, *:after { box-sizing: inherit; } 3 | 4 | html { 5 | line-height: 1.15; 6 | -webkit-text-size-adjust: 100%; 7 | } 8 | 9 | body { 10 | margin: 0; 11 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "SF Pro Text", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 12 | } 13 | 14 | h1 { margin: 0.67em 0; } 15 | 16 | hr { 17 | box-sizing: content-box; 18 | height: 0; 19 | overflow: visible; 20 | } 21 | 22 | pre { 23 | font-family: monospace, monospace; 24 | font-size: 1em; 25 | } 26 | 27 | a { background-color: transparent; } 28 | 29 | abbr[title] { 30 | border-bottom: none; 31 | text-decoration: underline; 32 | text-decoration: underline dotted; 33 | } 34 | 35 | b, 36 | strong { 37 | font-weight: bolder; 38 | } 39 | 40 | code, 41 | kbd, 42 | samp { 43 | font-family: monospace, monospace; 44 | font-size: 1em; 45 | } 46 | 47 | small { font-size: 80%; } 48 | 49 | sub, 50 | sup { 51 | font-size: 75%; 52 | line-height: 0; 53 | position: relative; 54 | vertical-align: baseline; 55 | } 56 | 57 | sub { bottom: -0.25em; } 58 | 59 | sup { top: -0.5em; } 60 | 61 | img { border-style: none; } 62 | 63 | button, 64 | input, 65 | textarea { 66 | font-family: inherit; 67 | font-size: 100%; 68 | line-height: 1.15; 69 | margin: 0; 70 | } 71 | 72 | button, 73 | input { 74 | overflow: visible; 75 | } 76 | 77 | button, 78 | select { 79 | text-transform: none; 80 | } 81 | 82 | button, 83 | [type="button"], 84 | [type="reset"], 85 | [type="submit"] { 86 | -webkit-appearance: button; 87 | } 88 | 89 | button::-moz-focus-inner, 90 | [type="button"]::-moz-focus-inner, 91 | [type="reset"]::-moz-focus-inner, 92 | [type="submit"]::-moz-focus-inner { 93 | border-style: none; 94 | padding: 0; 95 | } 96 | 97 | button:-moz-focusring, 98 | [type="button"]:-moz-focusring, 99 | [type="reset"]:-moz-focusring, 100 | [type="submit"]:-moz-focusring { 101 | outline: 1px dotted ButtonText; 102 | } 103 | -------------------------------------------------------------------------------- /src/screen.css: -------------------------------------------------------------------------------- 1 | /* Skip to Content 2 | ------------------------------------------------------------------- */ 3 | 4 | .skip-to-content { 5 | position: absolute; 6 | width: 1px; 7 | height: 1px; 8 | margin: 0; 9 | overflow: hidden; 10 | clip: rect(1px, 1px, 1px, 1px); 11 | } 12 | 13 | .skip-to-content:focus { 14 | z-index: 10000; 15 | width: auto; 16 | height: auto; 17 | clip: auto; 18 | } 19 | 20 | 21 | .layout { 22 | overflow: auto; 23 | -webkit-overflow-scrolling: touch; 24 | height: 100vh; 25 | } 26 | 27 | 28 | /* Navigation 29 | ------------------------------------------------------------------- */ 30 | 31 | #index { 32 | position: relative; 33 | overflow: auto; 34 | -webkit-overflow-scrolling: touch; 35 | padding: 0; 36 | background-color: #eee; 37 | counter-reset: nav-counter; 38 | } 39 | 40 | #index p { 41 | font-size: 1.0rem; 42 | } 43 | 44 | #index ol { 45 | list-style-type: none; 46 | margin: 0; 47 | padding: 0; 48 | } 49 | 50 | #index nav { 51 | display: none; 52 | } 53 | 54 | #index nav.open { 55 | display: block; 56 | } 57 | 58 | #index nav a { 59 | position: relative; 60 | display: block; 61 | border-bottom: 1px solid transparent; 62 | padding: 0.5rem 2rem; 63 | padding-left: calc(2rem + env(safe-area-inset-left)); 64 | padding-right: calc(2rem + env(safe-area-inset-right)); 65 | color: #333; 66 | font-size: 1.0rem; 67 | font-family: "SF Mono", "SFMono-Regular", "Roboto Mono", Consolas, "Liberation Mono", Menlo, Courier, monospace; 68 | line-height: 1.2; 69 | text-decoration: none; 70 | } 71 | 72 | #index nav a::before { 73 | display: block; 74 | margin-bottom: 2px; 75 | counter-increment: nav-counter; 76 | content: "Lab " counter(nav-counter); 77 | opacity: 0.5; 78 | text-transform: uppercase; 79 | font-size: 12px; 80 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "SF Pro Text", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 81 | } 82 | 83 | #index nav a.active::after { 84 | content: ''; 85 | position: absolute; 86 | top: 50%; 87 | left: 10px; 88 | left: calc(10px + env(safe-area-inset-left)); 89 | display: block; 90 | width: 10px; 91 | height: 10px; 92 | border-radius: 10px; 93 | margin-top: -5px; 94 | background: blue; 95 | } 96 | 97 | #index nav a:hover { 98 | color: #fff; 99 | background: blue; 100 | } 101 | 102 | #index nav a:hover::before { 103 | color: #fff; 104 | opacity: 1; 105 | } 106 | 107 | #index nav li:last-child a { 108 | padding-bottom: 2rem; 109 | } 110 | 111 | #index .link-home { 112 | position: -webkit-sticky; 113 | position: sticky; 114 | top: 0; 115 | margin: 0; 116 | padding: 2rem 0; 117 | z-index: 1; 118 | background-color: #eee; 119 | } 120 | 121 | #index .link-home a { 122 | display: block; 123 | margin-top: 1rem; 124 | margin-left: 2rem; 125 | padding-top: 1rem; 126 | padding-left: env(safe-area-inset-left); 127 | color: #333; 128 | text-decoration: none; 129 | } 130 | 131 | #index .link-menu { 132 | position: fixed; 133 | top: 1rem; 134 | right: 1rem; 135 | border: 1px solid #bbb; 136 | border-radius: 0.25rem; 137 | padding: 0.25rem 0.75rem; 138 | font-size: 14px; 139 | background: #fff; 140 | z-index: 2; 141 | transition: all 0.25s; 142 | } 143 | 144 | #index .link-menu:active { 145 | color: #000; 146 | transform: scale(0.9); 147 | } 148 | 149 | #index .link-home a span { 150 | border-bottom: 1px solid rgba(0,0,0, 0.15); 151 | padding-bottom: 3px; 152 | } 153 | 154 | 155 | /* Main Content 156 | ------------------------------------------------------------------- */ 157 | 158 | #content { 159 | position: relative; 160 | overflow: auto; 161 | -webkit-overflow-scrolling: touch; 162 | padding: 2rem 2rem 0 2rem; 163 | padding-left: calc(2rem + env(safe-area-inset-left)); 164 | padding-right: calc(2rem + env(safe-area-inset-right)); 165 | flex-grow: 1; 166 | counter-reset: content-heading-counter; 167 | outline: none; 168 | } 169 | 170 | #content p, 171 | #content li, { 172 | line-height: 125%; 173 | } 174 | 175 | #content li { 176 | margin-bottom: 0.5rem; 177 | } 178 | 179 | h1 { 180 | border-bottom: 1px solid #ccc; 181 | padding-bottom: 1rem; 182 | font-size: 2.0rem; 183 | } 184 | 185 | h1 em { 186 | display: block; 187 | align-self: center; 188 | margin: 0 20px 0 0; 189 | color: #888; 190 | font-size: 14px; 191 | font-style: normal; 192 | text-transform: uppercase; 193 | line-height: 1; 194 | } 195 | 196 | .file, 197 | .sample { 198 | overflow: auto; 199 | display: block; 200 | margin-bottom: 1em; 201 | border-radius: 5px; 202 | padding: 1rem; 203 | font-size: 14px; 204 | font-family: "SF Mono", "SFMono-Regular", "Roboto Mono", Consolas, "Liberation Mono", Menlo, Courier, monospace; 205 | line-height: 1.35; 206 | } 207 | 208 | .sample { 209 | color: #74e83f; 210 | background: #2E2E2E; 211 | } 212 | 213 | .file { 214 | margin-top: 0; 215 | margin-bottom: 2rem; 216 | border: 1px solid #eee; 217 | border-top-left-radius: 0; 218 | border-top-right-radius: 0; 219 | background: #f9f9f9; 220 | } 221 | 222 | code { 223 | border-radius: 3px; 224 | padding: 1px 3px; 225 | font-family: "SF Mono", "SFMono-Regular", "Roboto Mono", Consolas, "Liberation Mono", Menlo, Courier, monospace; 226 | background: #eee; 227 | } 228 | 229 | .note { 230 | display: block; 231 | min-height: 70px; 232 | border-top: 1px solid #ccc; 233 | border-bottom: 1px solid #ccc; 234 | padding: 1rem 0 1rem 40px; 235 | line-height: 1.5; 236 | background-image: url('data:image/svg+xml;utf8,'); 237 | background-repeat: no-repeat; 238 | background-position: 0 1rem; 239 | } 240 | 241 | h2 { 242 | padding-top: 1rem; 243 | color: blue; 244 | font-size: 1.75rem; 245 | counter-increment: content-heading-counter; 246 | } 247 | 248 | h2 + ul { 249 | padding-left: 0; 250 | list-style-type: circle; 251 | } 252 | 253 | h3 { 254 | font-size: 12px; 255 | font-weight: 400; 256 | text-transform: uppercase; 257 | } 258 | 259 | h3 em { 260 | font-family: "SF Mono", "SFMono-Regular", "Roboto Mono", Consolas, "Liberation Mono", Menlo, Courier, monospace; 261 | font-size: 110%; 262 | font-style: normal; 263 | text-transform: none; 264 | } 265 | 266 | h3 b { 267 | padding: 2px 6px; 268 | border-radius: 4px; 269 | color: #fff; 270 | background: rgb(150, 150, 150); 271 | } 272 | 273 | .file-heading { 274 | margin-bottom: 0; 275 | border: 1px solid #eee; 276 | border-bottom: none; 277 | border-top-left-radius: 7px; 278 | border-top-right-radius: 7px; 279 | padding-top: 1rem; 280 | padding-left: 1rem; 281 | padding-bottom: 0.75rem; 282 | background: #fff; 283 | } 284 | 285 | .file-heading.clickable { 286 | cursor: pointer; 287 | } 288 | 289 | .instructions { 290 | overflow: auto; 291 | } 292 | .instructions pre { 293 | margin: 0 0 0.5rem 0; 294 | } 295 | .instructions pre::before { 296 | content: '$'; 297 | display: inline; 298 | margin-right: 0.25rem; 299 | color: green; 300 | font-weight: bold; 301 | } 302 | 303 | .instructions pre:last-of-type { 304 | margin-bottom: 1.5rem; 305 | } 306 | 307 | .instructions .clickable { 308 | cursor: pointer; 309 | padding: 2px 0; 310 | border-radius: 2px; 311 | } 312 | 313 | .instructions pre.clickable:hover { 314 | background: #eee; 315 | } 316 | 317 | 318 | 319 | /* Footer 320 | ------------------------------------------------------------------- */ 321 | 322 | .foot-navigation { 323 | position: -webkit-sticky; 324 | position: sticky; 325 | bottom: 0; 326 | display: flex; 327 | width: calc(100% + 4rem); 328 | margin-left: -2rem; 329 | background: rgba(255,255,255, 0.95) 330 | } 331 | 332 | .foot-navigation > span { 333 | width: 50%; 334 | } 335 | 336 | .foot-navigation a { 337 | display: flex; 338 | flex-direction: column; 339 | width: 50%; 340 | padding: 1.75rem 2rem 0.75rem 2rem; 341 | color: #333; 342 | text-decoration: none; 343 | } 344 | 345 | .foot-navigation a:hover { 346 | color: #fff; 347 | background: blue; 348 | } 349 | 350 | .foot-navigation a:hover svg path { 351 | fill: #fff; 352 | } 353 | 354 | .foot-navigation a svg { 355 | display: block; 356 | } 357 | 358 | .foot-navigation a:first-child svg { 359 | transform: rotate(-90deg); 360 | } 361 | 362 | .foot-navigation a:last-child { 363 | align-items: flex-end; 364 | } 365 | 366 | .foot-navigation a:last-child svg { 367 | transform: rotate(90deg); 368 | } 369 | 370 | 371 | /* Desktop Layout Modifiers 372 | ------------------------------------------------------------------- */ 373 | 374 | @media only screen and (min-width: 950px) { 375 | #content { 376 | padding-left: 5.5rem; 377 | } 378 | 379 | .foot-navigation { 380 | width: calc(100% + 7.5rem); 381 | margin-left: -5.5rem; 382 | } 383 | 384 | .layout { 385 | display: flex; 386 | } 387 | 388 | #index { 389 | min-width: 340px; 390 | max-width: 340px; 391 | flex-shrink: 0; 392 | } 393 | 394 | #index nav { 395 | display: block; 396 | } 397 | 398 | .link-menu { 399 | display: none; 400 | } 401 | 402 | #index .link-home { 403 | padding-top: 4rem; 404 | } 405 | 406 | h1 { 407 | font-size: 2.75rem; 408 | } 409 | 410 | h2 { 411 | position: relative; 412 | font-size: 2.25rem; 413 | } 414 | 415 | h2::before { 416 | position: absolute; 417 | left: -3.5rem; 418 | display: inline-block; 419 | width: 2.5rem; 420 | height: 2.5rem; 421 | margin-top: 0.05rem; 422 | border-radius: 2rem; 423 | line-height: 2.3rem; 424 | text-align: center; 425 | color: #fff; 426 | font-size: 1.5rem; 427 | background: blue; 428 | content: counter(content-heading-counter); 429 | } 430 | } 431 | -------------------------------------------------------------------------------- /templates/index.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Git Immersion 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 23 | 24 | 25 | 26 |

Git Immersion

27 | 28 |

A guided tour that walks through the fundamentals of Git, inspired by the premise that to know a thing is to do it.

29 | 30 |
31 | 32 |

Git is a powerful, sophisticated system for distributed version control. Gaining an understanding of its features opens to developers a new and liberating approach to source code management. The surest path to mastering Git is to immerse oneself in its utilities and operations, to experience it first-hand.

33 | 34 | 39 | 40 | Start Git Immersion 41 | 42 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /templates/lab.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Lab <%= lab.number %> - Git Immersion 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 23 | 24 | 25 | 26 | 27 | 28 | Skip to content 29 | 30 | 31 |
32 |
33 | 36 | 37 | 42 |
43 | 44 |
45 | <%= lab_html %> 46 | <%= nav_links(f, lab) %> 47 |
48 |
49 | 50 | 51 | -------------------------------------------------------------------------------- /templates/lab_index.html.erb: -------------------------------------------------------------------------------- 1 | <% labs.each do |l| -%> 2 | 3 | <% end -%> 4 | -------------------------------------------------------------------------------- /templates/nav.html.erb: -------------------------------------------------------------------------------- 1 | 20 | --------------------------------------------------------------------------------