From 9e2e4b51338017fa9c91effe42951e1a195c9560 Mon Sep 17 00:00:00 2001 From: Thomas Hawes <105276609+thawes-rse@users.noreply.github.com> Date: Tue, 21 Mar 2023 17:31:10 +0000 Subject: [PATCH 1/6] Change instructions for previewing Markdown in VS Code (#51) --- _episodes/06_example_repo.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/_episodes/06_example_repo.md b/_episodes/06_example_repo.md index f5d1efb..623bac7 100644 --- a/_episodes/06_example_repo.md +++ b/_episodes/06_example_repo.md @@ -50,10 +50,10 @@ point out bits of Markdown syntax as we go. > #### Previewing in VS Code > > If you're using VS Code as your text editor, you can preview a rendered Markdown -> document by opening the Command Palette (_View_ > _Command Palette..._), -> searching for _Markdown: Open Preview_ and selecting this command. There's also -> and option to open the preview side-by-side with the Markdown source document, -> which is useful when editing. +> document by right-clicking on the file name in the Explorer pane and selecting +> _Open Preview_. See the +> VS Code documentation on viewing Markdown files +> for more information. > > If you're using VS Code to view Markdown documents, note that not all aspects > of GitHub Flavoured Markdown will render correctly in VS Code. For example, task lists are not From 470ca4f99eab780117a2404f5715403cbf8ec2b3 Mon Sep 17 00:00:00 2001 From: Thomas Hawes <105276609+thawes-rse@users.noreply.github.com> Date: Wed, 22 Mar 2023 14:10:11 +0000 Subject: [PATCH 2/6] Update episode timings (#54) --- _episodes/03_git_vs_github.md | 2 +- _episodes/04_configuring_git.md | 2 +- _episodes/06_example_repo.md | 2 +- _episodes/07_recording_changes.md | 2 +- _episodes/08_history_and_changes.md | 2 +- _episodes/09_recording_changes_revisited.md | 2 +- _episodes/10_pushing_and_pulling.md | 2 +- _episodes/11_undoing_changes.md | 2 +- _episodes/12_ignoring_files.md | 2 +- _episodes/13_local_branches.md | 2 +- _episodes/14_remote_branches_with_github.md | 2 +- _episodes/15_collaborating_with_branches.md | 2 +- _episodes/16_merge_conflicts.md | 2 +- _episodes/17_github_issues.md | 2 +- 14 files changed, 14 insertions(+), 14 deletions(-) diff --git a/_episodes/03_git_vs_github.md b/_episodes/03_git_vs_github.md index 75e914f..65b2ba5 100644 --- a/_episodes/03_git_vs_github.md +++ b/_episodes/03_git_vs_github.md @@ -3,7 +3,7 @@ layout: page title: "Git vs GitHub" order: 3 session: 1 -length: 5 +length: 10 toc: true adapted: false --- diff --git a/_episodes/04_configuring_git.md b/_episodes/04_configuring_git.md index 46e76eb..8bcd009 100644 --- a/_episodes/04_configuring_git.md +++ b/_episodes/04_configuring_git.md @@ -3,7 +3,7 @@ layout: page title: "Setting up Git and GitHub" order: 4 session: 1 -length: 30 +length: 40 toc: true adapted: true attrib_name: Version Control with Git - Setting Up Git diff --git a/_episodes/06_example_repo.md b/_episodes/06_example_repo.md index 623bac7..240ba16 100644 --- a/_episodes/06_example_repo.md +++ b/_episodes/06_example_repo.md @@ -3,7 +3,7 @@ layout: page title: "An Example Repository" order: 6 session: 1 -length: 15 +length: 25 toc: true adapted: false --- diff --git a/_episodes/07_recording_changes.md b/_episodes/07_recording_changes.md index 78ef8cd..33af1fa 100644 --- a/_episodes/07_recording_changes.md +++ b/_episodes/07_recording_changes.md @@ -3,7 +3,7 @@ layout: page title: "Recording Changes" order: 7 session: 1 -length: 40 +length: 60 toc: true adapted: true attrib_name: Version Control with Git - Tracking Changes diff --git a/_episodes/08_history_and_changes.md b/_episodes/08_history_and_changes.md index 8e26358..e239067 100644 --- a/_episodes/08_history_and_changes.md +++ b/_episodes/08_history_and_changes.md @@ -3,7 +3,7 @@ layout: page title: "Viewing History and Changes" order: 8 session: 2 -length: 30 +length: 35 toc: true adapted: true attrib_custom: > diff --git a/_episodes/09_recording_changes_revisited.md b/_episodes/09_recording_changes_revisited.md index e5569ff..3155e24 100644 --- a/_episodes/09_recording_changes_revisited.md +++ b/_episodes/09_recording_changes_revisited.md @@ -3,7 +3,7 @@ layout: page title: "Recording Changes – Revisited" order: 9 session: 2 -length: 20 +length: 30 toc: true adapted: false --- diff --git a/_episodes/10_pushing_and_pulling.md b/_episodes/10_pushing_and_pulling.md index 7313ca0..973928b 100644 --- a/_episodes/10_pushing_and_pulling.md +++ b/_episodes/10_pushing_and_pulling.md @@ -3,7 +3,7 @@ layout: page title: "Pushing to and Pulling From the Remote Repository" order: 10 session: 2 -length: 10 +length: 30 toc: true adapted: false --- diff --git a/_episodes/11_undoing_changes.md b/_episodes/11_undoing_changes.md index ee5c618..7c5b6ab 100644 --- a/_episodes/11_undoing_changes.md +++ b/_episodes/11_undoing_changes.md @@ -3,7 +3,7 @@ layout: page title: "Undoing Changes" order: 11 session: 2 -length: 20 +length: 40 toc: true adapted: false --- diff --git a/_episodes/12_ignoring_files.md b/_episodes/12_ignoring_files.md index 6617485..82b8a38 100644 --- a/_episodes/12_ignoring_files.md +++ b/_episodes/12_ignoring_files.md @@ -3,7 +3,7 @@ layout: page title: "Ignoring Files" order: 12 session: 2 -length: 20 +length: 15 toc: true adapted: true attrib_name: Version Control with Git - Ignoring Things diff --git a/_episodes/13_local_branches.md b/_episodes/13_local_branches.md index 469b54d..b31c247 100644 --- a/_episodes/13_local_branches.md +++ b/_episodes/13_local_branches.md @@ -3,7 +3,7 @@ layout: page title: "Working with Local Branches" order: 13 session: 3 -length: 25 +length: 40 toc: true adapted: true attrib_name: Version Control with Git - Branching diff --git a/_episodes/14_remote_branches_with_github.md b/_episodes/14_remote_branches_with_github.md index 749bbb9..959a438 100644 --- a/_episodes/14_remote_branches_with_github.md +++ b/_episodes/14_remote_branches_with_github.md @@ -3,7 +3,7 @@ layout: page title: "Remote Branches with GitHub" order: 14 session: 3 -length: 30 +length: 45 toc: true adapted: false --- diff --git a/_episodes/15_collaborating_with_branches.md b/_episodes/15_collaborating_with_branches.md index 1c826de..e87c856 100644 --- a/_episodes/15_collaborating_with_branches.md +++ b/_episodes/15_collaborating_with_branches.md @@ -3,7 +3,7 @@ layout: page title: "Collaborating with Branches" order: 15 session: 3 -length: 45 +length: 60 toc: true adapted: false --- diff --git a/_episodes/16_merge_conflicts.md b/_episodes/16_merge_conflicts.md index d820eab..4498f58 100644 --- a/_episodes/16_merge_conflicts.md +++ b/_episodes/16_merge_conflicts.md @@ -3,7 +3,7 @@ layout: page title: "Merge Conflicts" order: 16 session: 3 -length: 25 +length: 30 toc: true adapted: false --- diff --git a/_episodes/17_github_issues.md b/_episodes/17_github_issues.md index 2c3f507..5508c49 100644 --- a/_episodes/17_github_issues.md +++ b/_episodes/17_github_issues.md @@ -3,7 +3,7 @@ layout: page title: "GitHub Issues" order: 17 session: 3 -length: 5 +length: 15 toc: true adapted: false --- From aca2d6821817ea4d701e73f3f1a044335f31808e Mon Sep 17 00:00:00 2001 From: Thomas Hawes <105276609+thawes-rse@users.noreply.github.com> Date: Thu, 11 May 2023 11:54:43 +0100 Subject: [PATCH 3/6] Hotfix: Add year to copyright on Unix cheat sheet --- resources.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources.md b/resources.md index 2696751..8f91d6a 100644 --- a/resources.md +++ b/resources.md @@ -116,7 +116,7 @@ a section on > #### Licence > >
    {% include schedule.html %} From 333950efdb5708bc0fe4495949e98b798a89bbb5 Mon Sep 17 00:00:00 2001 From: Linus Tata Date: Fri, 8 Dec 2023 15:16:55 +0000 Subject: [PATCH 5/6] change recommended autocrlf config for Windows Recommended configs now mean the configured repo would contain only LF for both Windows and MacOS/Linux --- _episodes/04_configuring_git.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_episodes/04_configuring_git.md b/_episodes/04_configuring_git.md index 8bcd009..2633c57 100644 --- a/_episodes/04_configuring_git.md +++ b/_episodes/04_configuring_git.md @@ -202,7 +202,7 @@ recommended: ``` - **Windows:** ``` bash - $ git config --global core.autocrlf false + $ git config --global core.autocrlf true ``` You can read more about this issue in the Pro Git book. From 59af8530d47c565552737a7a934713c5a702de08 Mon Sep 17 00:00:00 2001 From: Linus Tata Date: Mon, 18 Dec 2023 23:46:03 +0000 Subject: [PATCH 6/6] Re-add previously removed episodes (#62) * Change recommended autocrlf config for Windows (#60) Changes the Windows config recommendation to true, aligning it with the MacOS/Linux. * Add back in some removed episodes Also return `undoing changes` and `ignoring files` to their original order. Listed timings add up to more than is suitable for the schedule (needs to be updated for the May course). Did not add back in episodes 17 (github issues) and 18 (remote branches from git). --- _episodes/11_undoing_changes.md | 371 ++++++++++ ...ignoring_files.md => 12_ignoring_files.md} | 2 +- _episodes/13_local_branches.md | 347 +++++++++ _episodes/14_remote_branches_with_github.md | 544 ++++++++++++++ _episodes/15_collaborating_with_branches.md | 679 ++++++++++++++++++ _episodes/16_merge_conflicts.md | 455 ++++++++++++ _episodes/{12_wrapup.md => 17_wrapup.md} | 2 +- 7 files changed, 2398 insertions(+), 2 deletions(-) create mode 100644 _episodes/11_undoing_changes.md rename _episodes/{11_ignoring_files.md => 12_ignoring_files.md} (99%) create mode 100644 _episodes/13_local_branches.md create mode 100644 _episodes/14_remote_branches_with_github.md create mode 100644 _episodes/15_collaborating_with_branches.md create mode 100644 _episodes/16_merge_conflicts.md rename _episodes/{12_wrapup.md => 17_wrapup.md} (99%) diff --git a/_episodes/11_undoing_changes.md b/_episodes/11_undoing_changes.md new file mode 100644 index 0000000..7c5b6ab --- /dev/null +++ b/_episodes/11_undoing_changes.md @@ -0,0 +1,371 @@ +--- +layout: page +title: "Undoing Changes" +order: 11 +session: 2 +length: 40 +toc: true +adapted: false +--- + +## Episode objectives + +At the end of this episode you will know how to unstage file changes you didn't +mean to stage and how to undo accidental commits. + + +## Removing files from the staging area + +We've got quite a bit of outstanding stuff we could add to our cheatsheet +and good practice guide. Let's make a note of these things in a `TODO.txt` +file, which we'll put in the root folder of our repository: + +``` +TODO: + +Add note about staging multiple files with `git add` and `git diff` + +Add note to commit good practice about perils of `git add .` + +Add cheatsheet entries for `git rm` and `git mv` + +Add cheatsheet entries for pushing and pulling +``` + +We might as well tick off the first two items, so let's add the following content +to `Git-cheatsheet.md`: + +``` + +## Specifying multiple files + +`git path/to/directory` — Apply `` to all files in and descended + from `path/to/directory` + +Examples: + +`git add .` — Stage all changes to files in current directory or descended from + current directory. + +`git diff foo/` — Show diffs of all files in directory `foo` or descended from + `foo`. + +``` + +And we add the following to `Good-practice-guides/Commit-good-practice.md`: + +``` + +## Make sure you know what you're committing + +Take care when staging multiple files with e.g. `git add .` that you don't +stage changes you didn't mean to. Always check what you're committing with +`git diff` or `git status`. + +``` + +We go ahead and stage these, using our recently learnt syntax for staging +changes to multiple files (our working directory is the repository root +folder, as usual): + +``` +$ git add . +``` + +Wait! Something doesn't feel right... Let's check the status: + +``` +$ git status +On branch main +Your branch is up to date with 'origin/main'. + +Changes to be committed: + (use "git restore --staged ..." to unstage) + modified: Git-cheatsheet.md + modified: Good-practice-guides/Commit-good-practice.md + new file: TODO.txt + +``` + +Ah, no! We don't want to commit our `TODO.txt` file. This was just to help us +keep track of our work and it doesn't belong in the repository. Fortunately +we can remove changes to a file from the staging area. In fact, +`git status` tells us how to do this. The general command to use is: + +``` +git restore --staged +``` + +So let's use this on our `TODO.txt` file. We run the command, then check that +the only differences staged are for the cheatsheet and good practice guide + +``` +$ git restore --staged TODO.txt + +$ git diff --name-only --staged +Git-cheatsheet.md +Good-practice-guides/Commit-good-practice.md +``` + +That's better, now we can go ahead and commit. (Good thing we checked before +committing the first time round!) + +``` +$ git commit -m "Add material on basic pathspec usage (directories)" +[main 0984d2b] Add material on basic pathspec usage (directories) + 2 files changed, 21 insertions(+) +``` + +## Undoing commits + +What if we'd gone ahead and actually committed our `TODO.txt` file by accident? +Git offers a couple of ways to address this: + +* _Reverting_: Create a new commit that undoes the old commit + +* _Resetting_: Move `HEAD` back to a previous commit, so that all the later commits are + removed from the commit history. + + +### Reverting (undo a commit by making a new one) + +Let's suppose we've 'accidentally' made a new commit which puts `TODO.txt` under +version control, which we want to undo: + +``` +$ git log --oneline -3 +cc01bda (HEAD -> main) Add TODO.txt +0984d2b Add material on basic pathspec usage (directories) +92b2ac2 (origin/main, origin/HEAD) Create general good practice guides directory +``` + +The command + +``` +git revert +``` +can be used to create a new commit that undoes a previous ``. In our +case, we want to undo the commit where we added `TODO.txt`, i.e. commit +`cc01bda`. We'll run that shortly, but first we need to make sure to make +a temporary copy of `TODO.txt` and store it outside the repository. This is +because the revert will return the repository to the state before we'd added +`TODO.txt`, which will involve deleting the file. Having done this, we now +perform the reversion: + +``` +$ git revert cc01bda +``` + +Because `git revert` is actually making a new commit, our text editor pops into +life for us to write a commit message. It's been pre-populated with a +helpful message, telling us which commit is being reverted: + +``` +Revert "Add TODO.txt" + +This reverts commit cc01bdaf30a98d5bfaf5e43838d90522695f251e. + +# Please enter the commit message for your changes. Lines starting +# with '#' will be ignored, and an empty message aborts the commit. +# +# On branch main +# Your branch is ahead of 'origin/main' by 2 commits. +# (use "git push" to publish your local commits) +# +# Changes to be committed: +# deleted: TODO.txt +# +``` + +We could edit this if we wanted to, but the default is fine, so we just close +the file to complete the commit. + +``` +$ git revert cc01bda +[main bc9d190] Revert "Add TODO.txt" + 1 file changed, 9 deletions(-) + delete mode 100644 TODO.txt +``` + +Now our commit history includes our new 'reverting' commit. Note also that +`TODO.txt` has been deleted. + +``` +$ git log --oneline -4 +bc9d190 (HEAD -> main) Revert "Add TODO.txt" +cc01bda Add TODO.txt +0984d2b Add material on basic pathspec usage (directories) +92b2ac2 (origin/main, origin/HEAD) Create general good practice guides directory + +$ ls +Git-cheatsheet.md Good-practice-guides/ README.md +``` + +> #### Note on `git revert` +> +> The `revert` command will only work if the working tree and staging area have +> no changes in them. + + +### Resetting (move back to a previous commit and lose later ones) + +Reverting a commit is often considered a 'safe' way to undo a commit, because +the original, offending commit is not actually lost. This way, if we decide we +_did_ in fact want to make that commit, we can easily recover it (by doing +`git revert` on it). There is a +more destructive way to undo commit history that will get rid of the commits. + +The `git reset` command is used to move back our `HEAD` to an earlier commit. +The default form of the command is: + +``` +git reset +``` + +In effect, this will 'rewind' the commit history back to finish at the given +``, dropping later commits as if they'd never happened. However, it will +put all changes since `` in the working tree, giving us a chance to +work with the files as they currently are before the reset. + +For example, let's create an empty file `foo.txt`. We can do this using +the `touch` command within the root folder of our repository: + +``` +$ touch foo.txt +``` + +We'll make a commit of this new file and remove it via a reset. First, we make +the commit: + +``` +$ git add foo.txt + +$ git commit -m "Add foo.txt to practice resetting" +[main fcecec0] Add foo.txt to practice resetting + 1 file changed, 1 insertion(+) + create mode 100644 foo.txt + +$ ls +foo.txt Git-cheatsheet.md Good-practice-guides/ README.md +``` + +Let's now reset the commit history back to how it was before +we committed `TODO.txt`. We check the log to get the commit identifier, +perform our reset, then check the status of the repository: + +``` +$ git log --oneline -5 +fcecec0 (HEAD -> main) Add foo.txt to practice resetting +bc9d190 Revert "Add TODO.txt" +cc01bda Add TODO.txt +0984d2b Add material on basic pathspec usage (directories) +92b2ac2 (origin/main, origin/HEAD) Create general good practice guides directory + +$ git reset 0984d2b + +$ git log --oneline -5 +0984d2b (HEAD -> main) Add material on basic pathspec usage (directories) +92b2ac2 (origin/main, origin/HEAD) Create general good practice guides directory +5cf8321 Remove rubbish.txt +d26a698 Add some rubbish to try out 'git rm' +910bb79 Add note about '--name-only' option to 'git diff' +``` + +We can see that our new `HEAD`, i.e. our new 'current commit', is what we reset +to, namely `0984d2b`. What state will our working tree be in? The answer is that it +will contain the changes that would need to be made to recover the state of +the repository as it was at `HEAD` just before the reset (at the +now-removed commit `fcecec0`). In other words, we expect to see just the change +that adds `foo.txt`. We can verify this with `git status`: + +``` +$ git status +On branch main +Your branch is ahead of 'origin/main' by 1 commit. + (use "git push" to publish your local commits) + +Untracked files: + (use "git add ..." to include in what will be committed) + foo.txt + +nothing added to commit but untracked files present (use "git add" to track) +``` + +We don't want to keep this `foo.txt` file, so let's just delete it with the +usual Unix command `rm` to get a nice, clean working tree again. + +``` +$ rm foo.txt +``` + +After all that, we can move that TODO list back into the repository. We'll +finish by removing the tasks we've completed and add some new tasks, leaving our +list like so: + +``` +TODO: + +Add cheatsheet entries for `git rm` and `git mv` + +Add cheatsheet entries for pushing and pulling + +Add cheatsheet entries about undoing changes + +``` + +> #### Hard reset +> +> If you're really sure you don't need to keep the changes from later commits when +> resetting, then you can automatically discard them by using the `--hard` option: +> `git reset --hard `. This will set your working tree to the exact same state as +> the commit you're resetting to. It thus deletes the work you did after +> ``; be sure that this is what you want before running a hard reset! + + +### Warning: pushing after a reset + +Something to be aware of when using `git reset` is that you can't by default push to a +remote repository if the tip of your local `main` branch is pointing to a commit +that is behind where the remote repository (`origin/main`) is. + +For example, suppose we had reset our local repository to the commit _before_ +`origin/main`, i.e. commit `5cf8321`, "Remove rubbish.txt". If we then tried to push +our local repository to the remote one, Git would not complete the request and +complain to us with the following message: + +``` +$ git push origin +To https://github.com/jbloggs9999/git-good-practice.git + ! [rejected] main -> main (non-fast-forward) +error: failed to push some refs to 'https://github.com/jbloggs9999/git-good-practice.git' +hint: Updates were rejected because the tip of your current branch is behind +hint: its remote counterpart. Integrate the remote changes (e.g. +hint: 'git pull ...') before pushing again. +hint: See the 'Note about fast-forwards' in 'git push --help' for details. +``` + +The main reason for this is to avoid a situation where commit history in the +remote repository gets lost. This is especially important when collaborating +with others, which we'll cover in later episodes in this course. + +You can read more on the topic of resetting on +Atlassian's tutorial. + + +## Important note: sensitive data + +The methods for undoing commits discussed here are appropriate when the commit +you made in error contains changes you didn't want, but don't fundamentally matter +if they've been recorded in the repository. In fact, even with a reset, it is +possible to recover the commit, with some advanced Git use that involves +something called the +reflog. + +This means that, if you accidentally commit _sensitive data_, such as passwords +or confidential information, you cannot work on the basis that `git reset` or +`git revert` has removed the data from the repository. Moreover, if you +pushed the commits to a remote repository, the information will be stored there. + +In these cases, you need to use specialist tools to remove all traces of the +sensitive data from the repository. See +the GitHub documentation for information on this topic. diff --git a/_episodes/11_ignoring_files.md b/_episodes/12_ignoring_files.md similarity index 99% rename from _episodes/11_ignoring_files.md rename to _episodes/12_ignoring_files.md index 37130bd..82b8a38 100644 --- a/_episodes/11_ignoring_files.md +++ b/_episodes/12_ignoring_files.md @@ -1,7 +1,7 @@ --- layout: page title: "Ignoring Files" -order: 11 +order: 12 session: 2 length: 15 toc: true diff --git a/_episodes/13_local_branches.md b/_episodes/13_local_branches.md new file mode 100644 index 0000000..21ee8f6 --- /dev/null +++ b/_episodes/13_local_branches.md @@ -0,0 +1,347 @@ +--- +layout: page +title: "Working with Local Branches" +order: 13 +session: 2 +length: 40 +toc: true +adapted: true +attrib_name: Version Control with Git - Branching +attrib_link: http://erdavenport.github.io/git-lessons/10-branching.html +attrib_copywrite: Software Carpentry +attrib_license: CC-BY 4.0 +attrib_license_link: https://creativecommons.org/licenses/by/4.0/ +--- + +## Learning objectives + +At the end of this episode you will be able to explain what branches are and how you might use them. +You will also be able to demonstrate how to create an experimental branch and merge it back into the `main` branch. + + +## Repository files + +We've had quite a lot of episodes working on our `git-good-practice` repository. +We've included here copies of the files that we will build on from this episode +onwards. If you haven't been following along with all the examples or exercises, +we suggest updating the files in your repository with the contents of one of +the following archives: + +* As a Zip archive: git-good-practice.zip. + +* As a Tar archive: git-good-practice.tar. + +You should place the archive contents in your own `git-good-practice` repository +(note: make sure to preserve the directory structure: the file +`Commit-good-practice.md` should be placed in the subdirectory +`Good-practice-guides` of the repository root folder). Then commit the changes +to `main` and use `git push` to update your remote repository. + + +## Concept of branches + +Git branches are a core feature of the Git version control system. They allow you to create multiple lines of +development within a single repository, allowing you to work on multiple features or fixes simultaneously, without +affecting the main codebase. + +In simple terms, a branch is a separate series of commits of the codebase that diverges from the main codebase. You can think of +it as a separate timeline of changes that runs in parallel with the main timeline. Each branch contains a copy of the +entire codebase, with its own set of changes. + +![A Git branch]({{ site.url }}/images/branch.svg) + +Git branches are incredibly useful for collaborative development, as they allow multiple developers to work on +different features or fixes simultaneously, without stepping on each other's toes. They also provide a way to +experiment with new features or ideas without affecting the stability of the main codebase. + + +## Working with local branches + +We're going to add some new content to the cheatsheet, doing this in a new, dedicated +branch. The content we'll add will be about using branches, so we'll be recording +what we learn as we go. The material on branches in this episode concerns working +with _local_ branches: branches that are created in our local repository, rather +than remotely on GitHub. The next episode will look at remote branches in more +detail. + + +### Creating branches + +The general way to create a new branch in our local repository is: + +``` +git branch +``` + +where `` is the name of the branch we wish to work on. + +When you create a new branch, you can specify the starting point. By default, if you do not specify a +starting point, Git will create the new branch at the `HEAD` commit. + +The `HEAD` is a reference to the current commit in the branch you are currently working on. It is essentially a pointer +to the tip of the branch you have checked out, which can be moved to any commits in the branch. + +When you create a new branch at the current commit, Git creates a new branch pointer that points to the same commit as +the `HEAD`. This means that the new branch initially has the same code as the current branch, but it is a separate branch +that can be modified independently. + +It can be helpful to have a dual picture in your mind when it comes to branches, +thinking of them both as a series of commits and also a pointer to a particular +commit. + + +> #### Branching off a commit +> +> You can create a new branch at any commit in the repository's history. This can be useful if you want to create a new +> branch based on a specific version of your code, or if you want to experiment with changes from a previous commit +> without affecting the current branch. Here's how you can create a new branch at any commit: +> +> 1. Identify the commit you want to create the branch at: Use the `git log` command to view the commit history of the +> repository and find the commit identifier of the commit you want to create the new branch at. +> 2. Create a new branch: Use the `git branch` command with the commit identifier to create a new branch at that commit: +> +> ``` +> git branch +> ``` +> +> For example, to create a new branch called `experimental-branch` based on a commit with the identifier `abc1234`, you would +> run: +> +> ``` +> git branch experimental-branch abc1234 +> ``` +> +> This creates a new branch called experimental-branch at the specified commit. + +In our example, we create a new branch +off of our most recent commit, called `branches-material`: + +``` +$ git branch branches-material +``` + +This will shortly be the branch in which we add new content to the cheatsheet. + + +### Viewing branches + +We can view all the local branches we have in our local repository by running: + +``` +git branch --list +``` + +(Equivalently, we could use the short form `-l` for `--list`.) + +In our `git-good-practice` repository, this would show us: + +``` +$ git branch --list + branches-material +* main +``` + +The asterisk (\*) preceding "main" is used to indicate the currently checked out branch in your local repository. +Another way to find out which branch you have checked out is to run `git status`. + +> #### `main` is just a branch +> +> The main branch in Git is simply a branch like any other branch in your repository, created automatically +> to hold the initial commit of the repository's history. +> +> Because the main branch is created automatically and is the default branch, it is often used as the primary branch +> for a project's development. However, you can choose to rename the main branch or use a different branch as the +> primary branch if you prefer. + + +### Adding commits to a branch + +In order to work on a branch, we need to **checkout** the branch so that any +new commits we make are added to the branch. The general command for doing this +is: + +``` +git checkout +``` + +> #### Branching off a branch +> +> There is nothing stopping us from creating a new branch that starts on a different +> branch to `main`. For example, suppose you have checked out a branch called `feature-branch`, +> and you want to create a new branch called `bugfix-branch` on top of `feature-branch`. If +> you do not specify a starting point for the new branch, Git will create it at the current commit on +> `feature-branch`, i.e. the commit that `HEAD` is pointing to. + + +We now switch to our new branch `branches-material` so that +our new cheatsheet content will feature in this branch, rather than the branch +`main`: + +``` +$ git checkout branches-material +Switched to branch 'branches-material' +``` + +We're now ready to get to work in the branch `branches-material`. We add the +following content to `Git-cheatsheet.md` on what we just learned about creating +a branch. + +``` + +## Branches + +`git branch ` — Create a new local branch called `` based at the + current commit (i.e. at `HEAD`). + +``` + +We make a commit with the new change: + +``` +$ git add Git-cheatsheet.md + +$ git commit -m "Add entry about creating branches" +[branches-material 8124186] Add entry about creating branches + 1 file changed, 6 insertions(+) +``` + +We next add an entry to our cheatsheet about checking out a branch: + +``` +`git checkout ` — Check out the branch ``, so that new commits + are added to ``. + +``` + +Having committed this change, we now view the log to see our new commits: + +``` +$ git log --oneline -5 +51da8da (HEAD -> branches-material) Add entry about checking out a branch +8124186 Add entry about creating branches +42a9a32 (origin/main, origin/HEAD, main) Ignore TODO list file +0984d2b Add material on basic pathspec usage (directories) +92b2ac2 Create general good practice guides directory +``` + +We can see that the new commits have been added to the branch `branches-material` +and that we are now working on `branches-material` as indicated by +`HEAD -> branches-material`. We can also see that commits to `main` stop at +commit `42a9a32` shown by `origin/main, origin/HEAD, main`. + +We can verify that these new commits are not on the `main` branch by examining +the log of `main` directly. In general, we can run + +``` +git log [options] +``` + +to view the commit history contained in a specific branch ``, where +`[options]` are any optional arguments we want to include e.g `--oneline`. In +our example, we get the following history for the `main` branch: + +``` +$ git log --oneline -5 main +42a9a32 (origin/main, origin/HEAD, main) Ignore TODO list file +0984d2b Add material on basic pathspec usage (directories) +92b2ac2 Create general good practice guides directory +5cf8321 Remove rubbish.txt +d26a698 Add some rubbish to try out 'git rm' +``` + +This confirms that the new commits are not on the `main` branch. + +## Merging branches + +Let's now look at how we can incorporate the changes we've made in the +`branches-material` branch into our `main` branch. In Git parlance, we want +to **merge** the commit history in `branches-material` into the history of +the `main` branch. + +Merging is a way to bring together different streams of development and integrate +them into a cohesive whole. This is the key to using Git for collaboration, as +it allows multiple developers to work on different +aspects of a project simultaneously _and then collate their changes_. + +We do this with the `merge` command: + +``` +git merge +``` + +Here, `` is the name of the branch whose commits we want +to bring _into_ the branch we're currently on. + +![Merging branches]({{ site.url }}/images/merge.svg) + +In our example, we need to merge the branch `branches-material` into `main`. +To do this, we need to checkout the branch we want to merge _into_, i.e. +`main`: + +``` +$ git checkout main +Switched to branch 'main' +Your branch is up to date with 'origin/main'. +``` + +Now we can merge `branches-material` into `main`: + +``` +$ git merge branches-material +Updating 42a9a32..51da8da +Fast-forward + Git-cheatsheet.md | 9 +++++++++ + 1 file changed, 9 insertions(+) +``` + +Let's take another look at the log of `main`: + +``` +$ git log --oneline -5 +51da8da (HEAD -> main, branches-material) Add entry about checking out a branch +8124186 Add entry about creating branches +42a9a32 (origin/main, origin/HEAD) Ignore TODO list file +0984d2b Add material on basic pathspec usage (directories) +92b2ac2 Create general good practice guides directory +``` + +We can see that the commits from `branches-material` have been added to +`main`. In fact, Git has just moved `main` to now point to the same commit +as at the end of the `branches-material`, as seen by the line + +``` +51da8da (HEAD -> main, branches-material) Add entry about checking out a branch +``` + +Our `main` branch now has some commits that have not been pushed to the remote +repository, so we will now rectify that: + +``` +$ git push +Username for 'https://github.com': jbloggs9999 +Password for 'https://jbloggs9999@github.com': +Enumerating objects: 8, done. +Counting objects: 100% (8/8), done. +Delta compression using up to 8 threads +Compressing objects: 100% (6/6), done. +Writing objects: 100% (6/6), 765 bytes | 255.00 KiB/s, done. +Total 6 (delta 4), reused 0 (delta 0), pack-reused 0 +remote: Resolving deltas: 100% (4/4), completed with 2 local objects. +To https://github.com/jbloggs9999/git-good-practice.git + 42a9a32..51da8da main -> main +``` + +### Exercise + +Add another commit to the `branches-material` branch about merging branches. +You may wish to use the following text: + +``` +`git merge ` — Combine the commit history of `` + with that of the branch currently checked out. + +``` + +Once you've done that, bring the changes into `main` by merging the branch +`branches-material` into `main`. Finally, push the new commits on `main` to the +remote repository. diff --git a/_episodes/14_remote_branches_with_github.md b/_episodes/14_remote_branches_with_github.md new file mode 100644 index 0000000..62a71bb --- /dev/null +++ b/_episodes/14_remote_branches_with_github.md @@ -0,0 +1,544 @@ +--- +layout: page +title: "Remote Branches with GitHub" +order: 14 +session: 2 +length: 45 +toc: true +adapted: false +--- + + +## Learning objectives + +By the end of this episode you will be able to create remote branches using +GitHub and track these remote branches locally. You will also learn how to +merge remote branches using a pull request, as well as how to delete branches +in your local and remote repositories. + + +## Local and remote branches + +Branches can reside in our local repository and/or remote repository, in the +same way that commits can. An **upstream** branch is one which resides in the +remote repository and is tracked locally, meaning the local branch is linked to +the remote branch. Our local repository stores references to any remote branches, +prepending their names with `remotes/origin/` (or simply `origin/`). It should +be noted that these remote branches are not updated automatically - we need to +use `git fetch` to update them. + + +### Viewing branches + +We can list _all_ the branches that our local repository is aware of (both +local branches and remote branches) by using `git branch --all` (or +just `git branch -a`): + +``` +$ git branch -a + branches-material +* main + remotes/origin/HEAD -> origin/main + remotes/origin/main +``` + +There are three branches worth noting here, namely `main` (our local version of +`main`), `remotes/origin/main` (our remote version of `main`) and `branches-material` +(our newly created local branch). We can safely ignore +`remotes/origin/HEAD -> origin/main` for the time being. + +As we have only created `branches-material` locally, it does not have a remote +counterpart, unlike `main`. Using GitHub, we can verify there is no remote branch +called `branches-material`. The necessary steps are as follows: + +- **Step 1** Navigate to your repository on GitHub. + +- **Step 2** Click on _branch(es)_, above the list of files on the left-hand side, + as indicated in the following screenshot: + + ![Viewing branches on GitHub]({{ site.url }}/images/github-view-branches.png) + +- **Step 3** Click on _All branches_, located to the left of the green _New branch_ + button on the right-hand side of the screen — this will display a list of all + the branches in your remote repository. `branches-material` will be missing + from this list. + + +## Working with remote branches + +So far, we've seen how to create a local branch, commit to it and merge it into +another branch (e.g. into `main`). This branch didn't have an upstream branch +in the remote repository. We're now going to look at the case where we +use GitHub to create a branch in the _remote repository_, which we then +bring into our local repository to work with. This approach takes advantage of +useful functionality provided by GitHub, promoting collaborative working. We +will look at an alternative workflow that doesn't rely on the features GitHub +provides in a later episode. + +Now, let's add some material to the cheatsheet relating to working with remote +branches, using GitHub to drive this development. The basic flow for doing this +will be the following: + +* Create a remote branch on GitHub that will receive our additions to the + cheatsheet. + +* Work on the cheatsheet locally, then push the changes up to the remote branch. + +* Use GitHub to merge the work into the `main` branch in the remote repository, + using a pull request. + +In order to do this, we need to do the following: + +* Create a remote branch on GitHub. + +* Update our local repository from the remote repository, so that we have a + reference to the newly created remote branch. + +* Create a local branch that is set up to track the remote one. + +* Add new commits to the local branch corresponding to our work on the + cheatsheet. + +* Push these commits to the upstream remote branch. + +* Merge the remote branch into the remote `main` branch, using + a pull request on GitHub. + +* Update our local repository to reflect this change to the remote repository. + + +### Create a remote branch on GitHub + +In GitHub, the following steps allow you to create a new remote branch: + +- **Step 1** Navigate to your repository on GitHub. + +- **Step 2** Click on the dropdown, located to the left of _branches_, on the + left-hand side of the screen. + +- **Step 3** Select the branch you would like to create a branch from. (For this + course, this will typically be `main`. If it is `main`, this step + becomes redundant.) + +- **Step 4** Click on the dropdown again and type in the name of your new branch + where it says _Find or create a branch..._. + +- **Step 5** Click on _Create branch: new-branch from 'base-branch'_, where + _new-branch_ is the name of your new branch and _base-branch_ is the name of + the branch you are branching off of (e.g. `main`). + +In our example `git-good-practice` repository, let's suppose we've just created a +new remote branch called `remote-branches-material`, which is based on top of +`main`. Our local repository doesn't have any knowledge of this new branch, as +can be seen by listing the branches: + +``` +$ git branch -a + branches-material +* main + remotes/origin/HEAD -> origin/main + remotes/origin/main +``` + + +### Fetch the remote branch + +In order to update our local repository so that it has knowledge of the new +remote branch, we use the following command from within our local repository: + +``` +git fetch +``` + +(Like with `git push` and `git pull`, we can instead run `git fetch origin` to +be explicit about the reference to the remote repository.) +We won't go into too much detail about exactly what `git fetch origin` is doing. For +our purposes, we use it to inspect the remote repository for information about +any new branches, or commits that have been made in remote branches, that our +local repository doesn't yet know about. + +In our example, after running `git fetch` in our `git-good-practice` +repository, we see that information about the new remote `remote-branches-material` +has been retrieved: + +``` +$ git fetch +Username for 'https://github.com': jbloggs9999 +Password for 'https://jbloggs9999@github.com': +From https://github.com/jbloggs9999/git-good-practice + * [new branch] remote-branches-material -> origin/remote-branches-material + +$ git branch -a + branches-material +* main + remotes/origin/HEAD -> origin/main + remotes/origin/main + remotes/origin/remote-branches-material +``` + +However, this has only created a reference to the remote branch, indicated +by `remotes/origin/remote-branches-material` in the above output. We still need +to create a _local_ branch that will track the remote branch. + + +### Create a new tracking local branch + +In order to create a local version of the +`origin/remote-branches-material` branch where we can add commits, we can +perform the following checkout: + +``` +$ git checkout remote-branches-material +Switched to a new branch 'remote-branches-material' +branch 'remote-branches-material' set up to track 'origin/remote-branches-material'. +``` + +You may be surprised by this: after all, we've asked Git to checkout a branch +that doesn't actually exist! Fortunately, Git is smart enough to realise that +what we want to do is set up a new local branch that tracks the `origin/remote-branches-material` +remote branch. So it automatically creates a new local branch — called `remote-branches-material` — that +will track `origin/remote-branches-material`, and checks out this new local branch for us. We +can verify this by listing all the branches again: + +``` +$ git branch -a + branches-material + main +* remote-branches-material + remotes/origin/HEAD -> origin/main + remotes/origin/main + remotes/origin/remote-branches-material +``` + +### Add content to the local branch and push + +We are now set to add our new material to `Git-cheatsheet.md` about remote +branches. We modify the start of the subsection _Branches_ so that it now reads as +follows: + +``` +## Branches + +`git branch ` — Create a new branch called `` + based at the current commit (i.e. at `HEAD`). + +`git checkout ` — Check out the branch ``, so that new commits + are added to ``. + +- Can also be used to create and checkout a new local branch `` that + tracks an existing remote branch `origin/`. + +`git merge ` — Combine the commit history of `` + with that of the branch currently checked out. +``` + +Having included this addition, we commit to our local `remote-branches-material` branch. + + +> #### Markdown syntax +> +> Unordered lists are denoted by using a hyphen (`-`) or an asterisk (`*`), +> followed by a space, with the text in the list item following. Example: +> +> ``` +> - Foo +> - Bar +> - Baz +> ``` +> renders as: +> - Foo +> - Bar +> - Baz +> +> Text that flows over multiple lines in the source file, yet belongs to a single +> list item, should respect the indenting. For example: +> +> ``` +> * list entry with +> +> new line +> +> * the quick brown fox jumps over the +> lazy dog +> ``` +> +> renders as: +> +> * list entry with +> +> new line +> +> * the quick brown fox jumps over the +> lazy dog + +We're now in the position where our local branch +`remote-branches-material` is ahead of the remote branch +`origin/remote-branches-material` that it tracks, as can be seen from +the log on `remote-branches-material`: + +``` +$ git log --oneline -3 +5125372 (HEAD -> remote-branches-material) Add note about creating local tracking branches +3b918f2 (origin/remote-branches-material, origin/main, origin/HEAD, main, branches-material) Add entry about merging branches +51da8da Add entry about checking out a branch +``` + +In order to update an upstream remote branch with new commits in the local +tracking branch, we can use `git push` (or `git push origin`), like we did when working with +the `main` branch in the episode +[Pushing to and Pulling From the Remote Repository]({{ site.url }}/10_pushing_and_pulling/index.html). +Note however that this will push the commits _on the currently checked out branch_ to its upstream +remote branch. So, in general, if you have a local branch `` that tracks a +remote branch `origin/`, then in order to push `` to `origin/` we +need to first checkout ``. + + +> #### Alternative: specify the branch explicitly +> +> Alternatively, if you have a local branch `` that tracks a remote branch +> `origin/`, then you can run `git push origin ` from any local branch +> to push commits from `` to `origin/`. + + +In our example, we're already on the branch `remote-branches-material` that we want to push, +so we can just go ahead and do `git push`: + +``` +$ git push +Username for 'https://github.com': jbloggs9999 +Password for 'https://jbloggs9999@github.com': +Enumerating objects: 5, done. +Counting objects: 100% (5/5), done. +Delta compression using up to 8 threads +Compressing objects: 100% (3/3), done. +Writing objects: 100% (3/3), 438 bytes | 219.00 KiB/s, done. +Total 3 (delta 2), reused 0 (delta 0), pack-reused 0 +remote: Resolving deltas: 100% (2/2), completed with 2 local objects. +To https://github.com/jbloggs9999/git-good-practice.git + 3b918f2..5125372 remote-branches-material -> remote-branches-material +``` + +The last line of the `git push` message shows that we've successfully updated the remote +branch with the new commit. + + +## Merge remote branch into remote `main` + +Once we have pushed our changes to the remote branch, we can merge said remote +branch into remote `main` by means of a pull request on GitHub. We can create and +complete a pull request (PR) as follows: + +- **Step 1** Navigate to your repository on GitHub. + +- **Step 2** Click on the _Pull requests_ tab (third tab from the left). + +- **Step 3** Click on the green _New pull request_ button on the right-hand side + of the screen. + +- **Step 4** Ensure `main` has been chosen as _base_ and choose your remote branch, + which in this case is `remote-branches-material`, as _compare_. + +- **Step 5** Click on the green _Create pull request_ button. + +- **Step 6** GitHub will generate a title based on the name of the branch you + are comparing, but this can be changed. You are also welcome to add a + description where it says _Leave a comment_. + +- **Step 7** Click on the green _Create pull request_ button. + +- **Step 8** Click on the green _Merge pull request_ button, located near the + bottom of the page. + + +## Pull changes into our local repository + +We have just merged our branch into `main` in the remote repository. To see +the changes made to `main` in our local repository, we need to pull them from +the remote repository. + +To update `main`, we first check it out: + +``` +$ git checkout main +Switched to branch 'main' +Your branch is up to date with 'origin/main'. +``` + +There's an important point to make about the output here. It is stated that +`Your branch is up to date with 'origin/main'.`. This doesn't mean there aren't changes on the remote to +pull in. Instead, it means that Git is not aware of any extra commits in +`origin/main` compared to `main` _since last `fetch`ing from the remote repository_. + +So, let us fetch updates from the remote repository: + +``` +$ git fetch +Username for 'https://github.com': jbloggs9999 +Password for 'https://jbloggs9999@github.com': +remote: Enumerating objects: 1, done. +remote: Counting objects: 100% (1/1), done. +remote: Total 1 (delta 0), reused 0 (delta 0), pack-reused 0 +Unpacking objects: 100% (1/1), 667 bytes | 83.00 KiB/s, done. +From https://github.com/jbloggs/git-good-practice + 3b918f2..86ebbee main -> origin/main +``` + +We can see that our local repository is now aware of the change to the remote +`origin/main` branch by checking the status again: + +``` +$ git status +On branch main +Your branch is behind 'origin/main' by 2 commits, and can be fast-forwarded. + (use "git pull" to update your local branch) + +nothing to commit, working tree clean +``` + +In the +[Pushing to and Pulling From the Remote Repository]({{ site.url }}/10_pushing_and_pulling/index.html) +episode, we mentioned that `git pull` can be used to retrieve updates from the +remote repository. To be more precise, `git pull` is used to bring in commits +_in a remote branch into a corresponding local branch_. In general, if you have +a remote branch that has commits not yet in a local tracking +branch ``, then run the following command _with `` checked out_ +to update `` with these new commits: + +``` +git pull +``` + +(or, to be explicit about the remote repository, `git pull origin`). + +> #### `pull` automatically `fetch`es +> +> `git pull` actually performs a two step process on a branch +> ``. First, it runs a `git fetch` to retrieve all new commits, +> branches, etc. from the remote repository. Then, +> it merges the changes that have been fetched into the `origin/` into +> ``. As a result, we did not in fact need to use the `git fetch` command +> before using `git pull` above. + +We pull the changes to `origin/main` into our local `main` branch: + +``` +$ git pull +Username for 'https://github.com': jbloggs9999 +Password for 'https://jbloggs9999@github.com': +Updating 3b918f2..86ebbee +Fast-forward + Git-cheatsheet.md | 3 +++ + 1 file changed, 3 insertions(+) +``` + +We can now see from the log that our changes are fully reflected in `main`: + +``` +$ git log --oneline -5 +86ebbee (HEAD -> main, origin/main, origin/HEAD) Merge pull request #1 from jbloggs9999/remote-branches-material +5125372 (origin/remote-branches-material, remote-branches-material) Add note about creating local tracking branches +3b918f2 (branches-material) Add entry about merging branches +51da8da Add entry about checking out a branch +8124186 Add entry about creating branches +``` + +## Cleaning up + +All the work we've done in our branches has been incorporated into `main` (both +locally and in the remote repository). So, to clean up the state of the repository, +we going to delete the branches `branches-material` and `remote-branches-material`, +including the remote version of `remote-branches-material`, since these +no longer serve any purpose. + + +> ### Good practice: deleting old branches +> +> It is good practice to delete branches that are no longer required. This makes +> navigating a repository easier and makes it clear what work is still ongoing +> compared to work that has been finished. + + +### Deleting branches from a local repository + +The general commands for deleting branches are as follows: + +* For deleting _local_ branches: `git branch -d ` + +* For deleting _remote_ branches: `git branch -d -r ` + +In both cases, note that you can specify more than one branch by separating the +branch names by a space. + +There are a couple of important things to note about deleting branches: + +* You can't delete a branch you currently have checked out. So make sure you + checkout a different branch before deletion e.g. do deletions from `main`. + +* Deleting branches removes the commits contained in those branches which don't + feature in other branches. So, before deleting a branch, be sure that the + changes you want to keep have been merged into another branch e.g. `main`. + +We delete our local branches: + +``` +$ git branch -d branches-material remote-branches-material +Deleted branch branches-material (was 3b918f2). +Deleted branch remote-branches-material (was 5125372). +``` + +Then we delete our remote branch reference `origin/remote-branches-material`: + +``` +$ git branch -r -d origin/remote-branches-material +Deleted remote-tracking branch origin/remote-branches-material (was 5125372). +``` + +> ### Force deletion +> +> Git may stop you from deleting a branch, because it can't verify that +> all the commits in the branch have been merged into a corresponding remote +> branch or another local branch. This is a protection +> mechanism to stop you from potentially losing changes. If you encounter this but +> are sure you want to proceed with the deletion, you can force the deletion +> by using `-D` instead of `-d` in the commands above. + + +Our local repository is now looking cleaner with regards to the branches we have: + +``` +$ git branch -a +* main + remotes/origin/HEAD -> origin/main + remotes/origin/main +``` + +Note also that the references to the non-`main` branches have disappeared from +the log (note that the _commits_ have _not_ disappeared, however, because these +are part of `main`): + +``` +$ git log --oneline -5 +86ebbee (HEAD -> main, origin/main, origin/HEAD) Merge pull request #1 from jbloggs9999/remote-branches-material +5125372 Add note about creating local tracking branches +3b918f2 Add entry about merging branches +51da8da Add entry about checking out a branch +8124186 Add entry about creating branches +``` + + +### Deleting branches from the remote repository via GitHub + +Finally, we need to delete the remote branch `remote-branches-material` from the +remote repository in GitHub. The necessary steps are as follows: + +- **Step 1** Navigate to your respository on GitHub. + +- **Step 2** Click on _branches_, above the list of files on the left-hand side. + +- **Step 3** Click on _All branches_, located to the left of the green _New branch_ + button on the right-hand side of the screen. + +- **Step 4** Identify the branch to be deleted and click on the appropriate + _bin icon_ on the right-hand side of the screen. If you hover over the icon, + it will show a message stating _Delete chosen-branch_, where _chosen-branch_ + is the name of the branch to be deleted. diff --git a/_episodes/15_collaborating_with_branches.md b/_episodes/15_collaborating_with_branches.md new file mode 100644 index 0000000..cfce603 --- /dev/null +++ b/_episodes/15_collaborating_with_branches.md @@ -0,0 +1,679 @@ +--- +layout: page +title: "Collaborating with Branches" +order: 15 +session: 2 +length: 60 +toc: true +adapted: false +--- + +## Learning objectives + +By the end of this episode you will have learned about a simple strategy +for using branches to collaborate with others on a shared codebase, called +feature branching. You will also have worked through an example of how this +strategy works in practice. + + +## Collaborating with others + +So far, we've only been using Git and GitHub as a solo developer. But the real +power of version control systems is realised when collaborating with other +developers on a shared project. Branching in Git provides the means for multiple +people in a way that allows them to work concurrently and bring their work together +in a controlled, transparent way. If you're using GitHub to host your remote +repository, then pull requests provide a way to communicate to others about +your changes. + +The key thing to remember when collaborating with Git is that, while there may +only be a single remote repository, _every developer has their own local repository_ that +is linked to the remote repository. The corollary to this is: + +* each developer is responsible for pushing the work from their local repository + to the remote one; and + +* each developer needs to pull in work that others have contributed to the + remote repository into their local repository. + +It's important that this is done promptly and regularly so that any changes +are not missed by you or others. + +There are different strategies and philosophies on how branches can be used +for collaboration. For this course, we're going to discuss a simple strategy +called _feature branching_. + + +## Feature branching + +A **feature** in this context is any piece of work that adds to the +software's overall development, whether this be a new piece of functionality, a +bug fix, some documentation, etc. In feature branching, new features are +developed in their own, dedicated **feature branches** that branch off the +`main` branch. When the feature is ready to be shared with others, the feature +branch is merged back into `main`. + +The `main` branch and feature branches take on different roles: + +* **The `main` branch**: This includes code changes that you want to share with + each other, or that are ready for release into the world. You should consider + it the 'neat' version of your work. As a general rule, _code only makes it into + `main` through merging a feature branch. You don't commit directly to `main`._ + +* **Feature branches**: These are branches where you work on code to develop + features, so will contain 'work in progress' until they're ready to merge + back into `main`. + +A common scenario you will come across when working on a feature branch is +where `main` gets updated through someone else merging a feature branch they're +working on. When it comes to you merging your feature branch into `main`, the +golden rule is to make sure you merge any changes to `main` _into_ your +feature branch _before_ merging your feature branch into `main`. This ensures +you are adding your work to the latest version of the 'common' codebase and +resolve any issues in your feature branch: + +* It gives you a chance to check that the changes you are making are consistent + with other peoples' work. Some changes made by others may pass under the radar + when merging e.g. different naming of functions / variables to what your + code relies on, deletion or moving of code your work relies on, etc. + +* It also gives you a chance to resolve any _merge conflicts_ that may arise + when you try to merge your work into the common codebase (about which more in + the [Merge Conflicts]({{ site.url }}/16_merge_conflicts/index.html) episode). + +An example of feature branching is depicted below: + +![Feature branching]({{ site.url }}/images/feature-branching.svg) + + +### Creating feature branches + +We will be creating feature branches using the workflow described in the previous +episode, [Remote Branches with GitHub]({{ site.url }}/14_remote_branches_with_github/index.html): +feature branches will be created remotely on GitHub and then fetched in for us +to work on locally. + + +### Protocol for merging feature branches into `main` + +Below we give steps for merging a feature branch `foo-feature` into `main`. We +assume `foo-feature` exists both as a remote branch in the remote repository and +also a local tracking branch in our local repository. + +1. Create a pull request on GitHub corresponding to the merge of `foo-feature` + into `main`. + +2. Pull any changes to `main` on the remote repository into your local version + of `main` (using `git pull` on your local `main` branch). + + a) If `main` was unchanged by the pull then go to step 2, + otherwise go to step b) below. + + b) If `main` got updated by the pull, then merge `main` into `foo-feature` + in your local repository before continuing, by using `git merge`. + If there are merge conflicts, these MUST be resolved and the merge into + `foo-feature` completed before continuing to step c) below (see the later + episode on [merge conflicts]({{ site.url }}/16_merge_conflicts/index.html) + for details on how to do this). Also take the + opportunity to make sure this merge hasn't introduced any problems into + the codebase (e.g. inconsistencies in naming, etc.) + + c) Pull the remote `main` into your local `main` again to be + sure no further changes were made while you were performing the merge in + step b). If the branch wasn't updated then proceed + to step 3 below, otherwise curse your luck and go back to step b). + +3. Push the commits in your local `foo-feature` branch to the corresponding + remote branch. + +4. Complete the pull request on GitHub to merge the `foo-feature` branch + into `main` on the remote. + +5. Pull the changes to `main` from the remote repository into your local repository. + Optional, but recommended: delete the feature branch `foo-feature` from GitHub + and from your remote repository (including the reference `origin/foo-feature` + to the remote branch). + + +## Example: Joe Bloggs and Jane Doe + +We're now going to assume that Joe Bloggs and Jane Doe are two people collaborating +on the `git-good-practice` repository. They will work with the same +remote repository, created under Joe's account, but will each have their own +associated local repositories. They're going to use the _feature branch_ +strategy, discussed in the previous section, to add: + +* Material to the cheatsheet about fetching remote branches from a remote + repository. + +* A new file documenting good practice when collaborating together. + + +In order for two people to work on the same remote repository on GitHub, they +each need to be listed as collaborators on the repository. + +> ### Collaborators on a GitHub repository +> +> If you are the owner of a repository on GitHub, you can invite collaborators +> to work on the repository using the +> instructions in the GitHub documentation. + + +### Exercise + +At this point in the course, we encourage participants to pair up and work +together on a common repository, taking the roles of Joe and Jane. + +So, find a partner to work with. Have one of you invite the other as a collaborator +on your repository. The collaborator should then clone the other person's +repository using the above instructions, so that you are both working on a +common remote repository. In what follows, one of you should play the role of +Joe Bloggs and the other of Jane Doe. + + +> #### Solo work +> +> If you are working through these course notes by yourself then try imagining +> you are two different developers collaborating on the repository together. We +> suggest you clone a fresh copy of the remote repository to a new local repository +> on your computer, while keeping your original local repository. This will +> help you simulate the scenario of collaborators each having their own local +> repository. + + +## Creating the branches + +Joe and Jane begin by creating remote branches on GitHub for their work, creating +these at the same time: + +* **Joe** creates a remote branch called `fetching-material`, which will contain + material about fetching from a remote repository. + +* **Jane** creates a remote branch called `collaboration-good-practice`, which + will contain work on good practice while collaborating. + +Next, Joe and Jane need to each create a local branch +that will track their remote branch. + +### Joe's local branch + +Joe fetches references to the new branches in the remote repository, then +creates a new local branch to track his remote `fetching-material` branch (and +also checks out this new local branch): + +``` +$ git fetch +Username for 'https://github.com': jbloggs9999 +Password for 'https://jbloggs9999@github.com': +From https://github.com/jbloggs9999/git-good-practice + * [new branch] collaboration-good-practice -> origin/collaboration-good-practice + * [new branch] fetching-material -> origin/fetching-material + +$ git checkout fetching-material +Switched to a new branch 'fetching-material' +branch 'fetching-material' set up to track 'origin/fetching-material'. +``` + +Notice that the fetch creates references to _both_ of the new remote branches, +`fetching-material` and `collaboration-good-practice`, which Joe and Jane created on +GitHub a moment ago. In contrast, Joe only has a local branch corresponding +to his remote `fetching-material` branch, since this is the branch he performed +the `git checkout` on: + +``` +$ git branch -a +* fetching-material + main + remotes/origin/HEAD -> origin/main + remotes/origin/collaboration-good-practice + remotes/origin/fetching-material + remotes/origin/main +``` + +### Jane's local branch + +Jane runs the analogous commands in her local repository, in this case creating +a local tracking branch for her `collaboration-good-practice` remote branch +instead: + +``` +$ git fetch +Username for 'https://github.com': janedoe9999 +Password for 'https://janedoe9999@github.com': +From https://github.com/jbloggs9999/git-good-practice + * [new branch] collaboration-good-practice -> origin/collaboration-good-practice + * [new branch] fetching-material -> origin/fetching-material + +$ git checkout collaboration-good-practice +Switched to a new branch 'collaboration-good-practice' +branch 'collaboration-good-practice' set up to track 'origin/collaboration-good-practice'. + +$ git branch -a +* collaboration-good-practice + main + remotes/origin/HEAD -> origin/main + remotes/origin/collaboration-good-practice + remotes/origin/fetching-material + remotes/origin/main +``` + +## Working on the feature branches + +### Joe's `fetching-material` feature branch + +Joe adds the following content to `Git-cheatsheet.md`: + +``` + +## Syncing with a remote repository + +`git fetch origin` — Retrieve references to new remote branches, and/or commits + that are contained in remote branches, from the remote + repository (referred to as `origin`). + +``` + +He then commits it on his (local) `fetching-material` branch: + +``` +$ git add Git-cheatsheet.md + +$ git commit -m "Add entry about fetching from a remote" +[fetching-material 1d026a8] Add entry about fetching from a remote + 1 file changed, 7 insertions(+) +``` + +Checking the status, Joe confirms that his local `fetching-material` branch is 1 commit +ahead of the associated remote branch: + +``` +$ git status +On branch fetching-material +Your branch is ahead of 'origin/fetching-material' by 1 commit. + (use "git push" to publish your local commits) + +nothing to commit, working tree clean +``` + +In order to back up his work and give Jane a preview of what he's been doing, +Joe pushes the changes to his local `fetching-material` branch to the remote +`origin/fetching-material`: + +``` +$ git push +Username for 'https://github.com': jbloggs9999 +Password for 'https://jbloggs9999@github.com': +Enumerating objects: 5, done. +Counting objects: 100% (5/5), done. +Delta compression using up to 8 threads +Compressing objects: 100% (3/3), done. +Writing objects: 100% (3/3), 494 bytes | 123.00 KiB/s, done. +Total 3 (delta 2), reused 0 (delta 0), pack-reused 0 +remote: Resolving deltas: 100% (2/2), completed with 2 local objects. +To https://github.com/jbloggs9999/git-good-practice.git + 86ebbee..1d026a8 fetching-material -> fetching-material +``` + +The history on Joe's `fetching-material` branch now looks like this: + +``` +$ git log --oneline -5 +1d026a8 (HEAD -> fetching-material, origin/fetching-material) Add entry about fetching from a remote +86ebbee (origin/main, origin/collaboration-good-practice, origin/HEAD, main) Merge pull request #1 from jbloggs9999/remote-branches-material +5125372 Add note about creating local tracking branches +3b918f2 Add entry about merging branches +51da8da Add entry about checking out a branch +``` + + +### Jane Doe and her `collaboration-good-practice` feature branch + +Jane creates a new file called `Collaboration-good-practice.md` in the +`Good-practice-guides` directory (which she includes as a stand-alone commit) +and adds the following content about the above feature branch strategy: + +``` +# Best practice for collaboration + +## A basic feature branch strategy + +A basic way to collaborate on a common repository is to use *feature branching*. +A *feature* in this context is any piece of work that adds to of the +software's overall development, whether this be a new piece of functionality, a +bug fix, some documentation, etc. In feature branching, new features are +developed in their own, dedicated *feature branches* that branch off the +`main` branch. When the feature is ready to be shared with others, the feature +branch is merged back into `main`. + +``` + +Like Joe, she then also pushes the new commits on her local +`collaboration-good-practice` to the associated remote branch, +`origin/collaboration-good-practice`: + +``` +$ git push origin +Username for 'https://github.com': janedoe9999 +Password for 'https://janedoe9999@github.com': +Enumerating objects: 10, done. +Counting objects: 100% (10/10), done. +Delta compression using up to 8 threads +Compressing objects: 100% (7/7), done. +Writing objects: 100% (8/8), 1.04 KiB | 353.00 KiB/s, done. +Total 8 (delta 2), reused 1 (delta 0), pack-reused 0 +remote: Resolving deltas: 100% (2/2), completed with 1 local object. +To https://github.com/jbloggs9999/git-good-practice.git + 86ebbee..b9df491 collaboration-good-practice -> collaboration-good-practice +``` + +Having done this, her `collaboration-good-practice` history looks as follows: + +``` +$ git log --oneline -5 +b9df491 (HEAD -> collaboration-good-practice, origin/collaboration-good-practice) Add material on feature branching +687cf02 Start good practice guide on collaboration +86ebbee (origin/main, origin/fetching-material, origin/HEAD, main) Merge pull request #1 from jbloggs9999/remote-branches-material +5125372 Add note about creating local tracking branches +3b918f2 Add entry about merging branches +``` + + +Let us take a moment to point out that, at this point, `origin/fetching-material` +hasn't been updated in Jane's local repository, and neither has +`origin/collaboration-good-practice` been updated in Joe's local repository. +This is because they haven't yet fetched updates to the corresponding remote +branches from the remote repository. This underlines the +fact that the information about remote branches only +gets updated in local repositories when you tell Git to retrieve updates from +the remote, via `git fetch` or `git pull`. + + +## Merging + +## First to the pass: Joe + +Joe finishes his work before Jane does and so gets to work on merging his +feature branch into the `main` branch. Following the strategy that was discussed +in the episode [Remote Branches with GitHub]({{ site.url }}/14_remote_branches_with_github/index.html), +he creates a pull request associated to the merge. + +Having done this, he checks that Jane hasn't +merged any work into the remote `main` branch. He could do this by examining +the history of `main` on GitHub, or by checking out `main` and +pulling in any changes from the remote: + +``` +$ git checkout main +Switched to branch 'main' +Your branch is up to date with 'origin/main'. + +$ git pull +Username for 'https://github.com': jbloggs9999 +Password for 'https://jbloggs9999@github.com': +remote: Enumerating objects: 10, done. +remote: Counting objects: 100% (10/10), done. +remote: Compressing objects: 100% (5/5), done. +remote: Total 8 (delta 2), reused 8 (delta 2), pack-reused 0 +Unpacking objects: 100% (8/8), 1.02 KiB | 17.00 KiB/s, done. +From https://github.com/jbloggs9999/git-good-practice + 86ebbee..b9df491 collaboration-good-practice -> origin/collaboration-good-practice +Already up to date. +``` + +Recall that `Your branch is up to date with 'origin/main'.` after the +checkout in the above output only means that Git is not aware of any extra commits in +`origin/main` compared to `main` since the last `fetch` or `pull`. In this +case, the `git pull` command confirms that the local `main` branch was already up to date with +the remote repository's `main` branch. It also shows that then new commits in the +`origin/collaboration-good-practice` that Jane's working on have been fetched. + +Since his `main` branch is fully up to date with +the remote version, Joe goes ahead and performs the merge of his feature branch +into `main` on GitHub, by completing the pull request. He then updates his local +`main` branch with the merged changes: + +``` +$ git pull +Username for 'https://github.com': jbloggs9999 +Password for 'https://jbloggs9999@github.com': +remote: Enumerating objects: 1, done. +remote: Counting objects: 100% (1/1), done. +remote: Total 1 (delta 0), reused 0 (delta 0), pack-reused 0 +Unpacking objects: 100% (1/1), 654 bytes | 93.00 KiB/s, done. +From https://github.com/jbloggs9999/git-good-practice + 86ebbee..4e209e9 main -> origin/main +Updating 86ebbee..4e209e9 +Fast-forward + Git-cheatsheet.md | 7 +++++++ + 1 file changed, 7 insertions(+) +``` + + +### Jane: merging after changes to `main` + +Jane is ready to merge her `collaboration-good-practice` feature branch into +`main`, so she creates a pull request linked to the remote feature branch. + +As the feature branch protocol recommends, she checks to see whether her local +`main` branch is up-to-date with the remote repository: + +``` +$ git checkout main +Switched to branch 'main' +Your branch is up to date with 'origin/main'. + +$ git pull origin +Username for 'https://github.com': janedoe9999 +Password for 'https://janedoe9999@github.com': +remote: Enumerating objects: 4, done. +remote: Counting objects: 100% (4/4), done. +remote: Compressing objects: 100% (2/2), done. +remote: Total 4 (delta 3), reused 2 (delta 2), pack-reused 0 +Unpacking objects: 100% (4/4), 974 bytes | 27.00 KiB/s, done. +From https://github.com/jbloggs9999/git-good-practice + 86ebbee..4e209e9 main -> origin/main + 86ebbee..1d026a8 fetching-material -> origin/fetching-material +Updating 86ebbee..4e209e9 +Fast-forward + Git-cheatsheet.md | 7 +++++++ + 1 file changed, 7 insertions(+) +``` + +(Again, she could also have done this by looking at the history of `main` on +GitHub.) Jane finds that there have been changes made to the `main` branch while +she was working on her feature branch, as indicated by the line + +``` + 86ebbee..4e209e9 main -> origin/main +``` + +Therefore, she merges her now updated `main` branch into her feature branch, to +ensure her feature branch includes the latest changes. (Note that she does +this _locally_, rather than on GitHub.) + +``` +$ git checkout collaboration-good-practice +Switched to branch 'collaboration-good-practice' +Your branch is up to date with 'origin/collaboration-good-practice'. + +$ git merge main +Merge made by the 'ort' strategy. + Git-cheatsheet.md | 7 +++++++ + 1 file changed, 7 insertions(+) +``` + +She also then pushes her updated feature branch to the +remote repository, so that her local and remote branches are synchronised. + +``` +$ git push +Username for 'https://github.com': janedoe9999 +Password for 'https://janedoe9999@github.com': +Enumerating objects: 4, done. +Counting objects: 100% (4/4), done. +Delta compression using up to 8 threads +Compressing objects: 100% (2/2), done. +Writing objects: 100% (2/2), 325 bytes | 325.00 KiB/s, done. +Total 2 (delta 1), reused 0 (delta 0), pack-reused 0 +remote: Resolving deltas: 100% (1/1), completed with 1 local object. +To https://github.com/jbloggs9999/git-good-practice.git + b9df491..ee1617c collaboration-good-practice -> collaboration-good-practice +``` + +Having done this, she now effectively starts the protocol for merging a feature +branch into `main` again. First she checks there haven't been any further updates +to `main`: + +``` +$ git checkout main +Switched to branch 'main' +Your branch is up to date with 'origin/main'. + +$ git pull +Username for 'https://github.com': janedoe9999 +Password for 'https://janedoe9999@github.com': +Already up to date. +``` + +Having seen there are no further updates, she goes to GitHub and completes +the associated pull request, thus merging her remote +`collaboration-good-practice` feature branch into `main` in the remote +repository. Then she pulls down the new, merged changes from `origin/main` into +her local `main` branch: + +``` +$ git pull +Username for 'https://github.com': janedoe9999 +Password for 'https://janedoe9999@github.com': +remote: Enumerating objects: 1, done. +remote: Counting objects: 100% (1/1), done. +remote: Total 1 (delta 0), reused 0 (delta 0), pack-reused 0 +Unpacking objects: 100% (1/1), 648 bytes | 108.00 KiB/s, done. +From https://github.com/jbloggs9999/git-good-practice + 4e209e9..785f6f8 main -> origin/main +Updating 4e209e9..785f6f8 +Fast-forward + Good-practice-guides/Collaboration-good-practice.md | 11 +++++++++++ + 1 file changed, 11 insertions(+) + create mode 100644 Good-practice-guides/Collaboration-good-practice.md +``` + +## Finishing up + +The merged changes added to `main` by Jane won't feature in Joe's local +repository until he pulls them into his local `main` branch. Having seen on +GitHub that Jane has completed her pull request, he duly makes sure his local +repository has these changes (making sure he's on `main` to begin with): + +``` +$ git status +On branch main +Your branch is up to date with 'origin/main'. + +nothing to commit, working tree clean + +$ git pull +Username for 'https://github.com': jbloggs9999 +Password for 'https://jbloggs9999@github.com': +remote: Enumerating objects: 5, done. +remote: Counting objects: 100% (5/5), done. +remote: Compressing objects: 100% (2/2), done. +remote: Total 3 (delta 2), reused 1 (delta 1), pack-reused 0 +Unpacking objects: 100% (3/3), 829 bytes | 48.00 KiB/s, done. +From https://github.com/jbloggs9999/git-good-practice + 4e209e9..785f6f8 main -> origin/main + b9df491..ee1617c collaboration-good-practice -> origin/collaboration-good-practice +Updating 4e209e9..785f6f8 +Fast-forward + Good-practice-guides/Collaboration-good-practice.md | 11 +++++++++++ + 1 file changed, 11 insertions(+) + create mode 100644 Good-practice-guides/Collaboration-good-practice.md +``` + +Both Joe and Jane now have completely up-to-date local repositories. + + +## Viewing a graph of history + +Feeling satisfied about a successful collaboration, Joe and Jane decide to take +a look at what they've just accomplished. One nice way to view history in the +repository when multiple branches are involved is to use the `--graph` option with `git log`. +This will pictorially represent the inter-relationships of the branches that +are involved in the commit history: + +``` +git log [options] --graph +``` + +Note: you may find it clearest to use this with the `--oneline` option. + +From within Joe's repository, the output of the graph looks like the following +(viewing only the last 7 commits): + + + +![Git log as a graph]({{ site.url }}/images/git-log-as-graph.png) + +Let's go through this output in more detail: + +* The commit `86ebbee` ("Merge pull request #1 ...") is where Joe and Jane + started their work. + +* The vertical line represents the `main` branch, that we're currently on. After + the starting commit, there are two more commits, `4e209e9` and `785f6f8`, that + show where the feature branches where merged into `main` on the remote. + +* We can see the commit on Joe's feature branch by following the purple line: + the commit is `1d026a8`, where `origin/fetching-material` currently points. (It's + also where the local branch `fetching-material` points, which we see because we + are viewing Joe's local repository.) The feature branch got merged into main + at commit `4e209e9` ("Merge pull request #2 from jbloggs9999/fetching-material") + +* Jane's feature branch commits start at `687cf02` ("Start good practice guide on + collaboration") and continue along the yellow line. Note that commit + `ee1617c` (where `origin/collaboration-good-practice` currently points) is + where Jane merged her `main` branch into `collaboration-good-practice` locally, + after pulling in Joe's merged changes to `main`. Note also the red line connecting + `main` to this commit, indicating Jane's merge. + +* Finally, the latest commit `785f6f8` ("Merge pull request #3 from jbloggs9999/collaboration-good-practice") + is the commit where Jane's feature branch got merged into main on the remote. + + +## Cleaning up + +At this point, Joe and Jane can delete their feature branches, both from the +local repository and the remote repository. There is no risk of losing work, +because everything has been merged into `main`, both locally and on the +remote repository. After doing this, the log of recent history looks +like this: + +``` +$ git log --oneline -7 +785f6f8 (HEAD -> main, origin/main, origin/HEAD) Merge pull request #3 from jbloggs9999/collaboration-good-practice +ee1617c Merge branch 'main' into collaboration-good-practice +4e209e9 Merge pull request #2 from jbloggs9999/fetching-material +b9df491 Add material on feature branching +687cf02 Start good practice guide on collaboration +1d026a8 Add entry about fetching from a remote +86ebbee Merge pull request #1 from jbloggs9999/remote-branches-material +``` diff --git a/_episodes/16_merge_conflicts.md b/_episodes/16_merge_conflicts.md new file mode 100644 index 0000000..2c5b650 --- /dev/null +++ b/_episodes/16_merge_conflicts.md @@ -0,0 +1,455 @@ +--- +layout: page +title: "Merge Conflicts" +order: 16 +session: 2 +length: 30 +toc: true +adapted: false +--- + +## Learning objectives + +By the end of this episode, you will understand what a merge conflict and how +these can be resolved, by working through a small example. You will also learn +some general tips that can help you manage or avoid conflicts when collaborating +with others on a common codebase. + + +## What a merge conflict is and how it can arise + +Let's suppose we have two branches, `branch-a` and `branch-b`, with `branch-b` +branching off of `branch-a` at commit `C`. As +commits get added to these two branches after `C`, it's possible that a particular file +will be modified on both branches in ways that are incompatible. For example: + +* The same line in a file might get edited on both `branch-a` and `branch-b` + in different ways. + +* Some material may be added to a file on one branch while the file is deleted on + the other branch. + +In cases such as these, when we try to merge `branch-b` back into `branch-a`, +Git has no way of determining which edits are the 'correct' version to take forward: from +its point of view, it has no reason to say why one set of edits should be +preferred over the other. In this case, Git will say that the file contains a +**merge conflict** (or just a **conflict**), and it will require us +to further specify how the conflict should be **resolved**. + +In this episode, we're going to add some material to the cheatsheet in our +`git-good-practice` repository about pushing and pulling +branches, but in such a way as to engineer a merge conflict. We'll then look at +how we could resolve it. + + +## Cooking up a conflict + +We'll start a new feature branch called `pushing-pulling` off of our last +commit to `main`. We create this on GitHub as a remote branch and then create +a local tracking branch: + +``` +$ git fetch +Username for 'https://github.com': jbloggs9999 +Password for 'https://jbloggs9999@github.com': +From https://github.com/jbloggs9999/git-good-practice + * [new branch] pushing-pulling -> origin/pushing-pulling + +$ git checkout pushing-pulling +Switched to a new branch 'pushing-pulling' +branch 'pushing-pulling' set up to track 'origin/pushing-pulling'. +``` + +Let's now add the following content to `Git-cheatsheet.md`: + +``` + +`git push origin` — Transfer commits in the current local branch to the + corresponding upstream branch in the remote repository. + +`git pull origin` — Bring commits from an upstream branch in the remote + repository into the current local branch. + +``` + +We commit these changes to the `pushing-pulling` feature branch and then push +them up to the upstream remote branch. Our log on `pushing-pulling` now looks +like the following: + +``` +$ git log --oneline -3 +991a78b (HEAD -> pushing-pulling, origin/pushing-pulling) Add entries on 'git push' and 'git pull' +785f6f8 (origin/main, origin/HEAD, main) Merge pull request #3 from jbloggs9999/collaboration-good-practice +ee1617c Merge branch 'main' into collaboration-good-practice +``` + +Let's now suppose that, while we were working on our `pushing-pulling` feature +branch, someone else pushed a commit to `main` that worked on the same section +of the `Git-cheatsheet.md` file. To emulate this scenario, we'll switch to the +`main` branch, commit the following addition to our cheatsheet (note how the +order of the `git push` and `git pull` entries has swapped around) and push +those changes up to the remote `main` branch: + +``` + +`git pull origin` — Bring commits from an upstream branch in the remote + repository into the current local branch. + +`git push origin` — Transfer commits in the current local branch to the + corresponding upstream branch in the remote repository. + +``` + +After doing that, our log looks like the following (note that we use the `--all` +option to view commits on all branches and display the log as a graph by using +the `--graph` option): + +``` +$ git log --oneline --graph --all -3 +* 23e5d3a (HEAD -> main, origin/main, origin/HEAD) Add material about pulling and pushing branches +| * 991a78b (origin/pushing-pulling, pushing-pulling) Add entries on 'git push' and 'git pull' +|/ +* 785f6f8 Merge pull request #3 from jbloggs9999/collaboration-good-practice +|\ +``` + +Let's now switch back to the feature branch and try merging `main` into it. If +we do that, we get the following output: + +``` +$ git checkout pushing-pulling +Switched to branch 'pushing-pulling' +Your branch is up to date with 'origin/pushing-pulling'. + +$ git merge main +Auto-merging Git-cheatsheet.md +CONFLICT (content): Merge conflict in Git-cheatsheet.md +Automatic merge failed; fix conflicts and then commit the result. +``` + +This message may look scary, but don't panic! In the next section, we'll look at +how to resolve this conflict. + + +## Resolving the conflict + +Before doing anything else, let's look at the current state of things with +`git status`: + +``` +$ git status +On branch pushing-pulling +Your branch is up to date with 'origin/pushing-pulling'. + +You have unmerged paths. + (fix conflicts and run "git commit") + (use "git merge --abort" to abort the merge) + +Unmerged paths: + (use "git add ..." to mark resolution) + both modified: Git-cheatsheet.md + +no changes added to commit (use "git add" and/or "git commit -a") +``` + +Git is actually giving us quite a lot of helpful information here: + +* Firstly, it's reminding us that we're on the branch `pushing-pulling` (we'll need to + keep this in mind). + +* Secondly, it's indicating that we're in the middle of a merge + (`You have unmerged paths.`) and that there are conflicts that need resolving. + It has listed out the files where conflicts have arisen + (`both modified: Git-cheatsheet.md`). Note that we only have one file with + conflicts in our example, but there could be multiple files more generally. + +* It's also giving us a clue (albeit somewhat vaguely) about how to complete + the merge. For each file with conflicts, we need to: + 1. Modify the file to fix the conflicts i.e. write in what we want the file + to contain going forward. + 2. Stage the fixed file with `git add` (this will 'mark the file as resolved') + + Then we need to commit our staged changes with `git commit`. This will complete + the merge process. + +* Finally, it's saying that if we want to abort and go back to how things + were just before running `git merge`, we can use the command + + ``` + git merge --abort + ``` + +Let's now fix the conflicts within `Git-cheatsheet.md`. If we open up the file +in a text editor, we will see the following content towards the end of the file, +around the place where we added content about pushing and pulling: + +``` +## Syncing with a remote repository + +`git fetch origin` — Retrieve references to new remote branches, and/or commits + that are contained in remote branches, from the remote + repository (referred to as `origin`). + +<<<<<<< HEAD +`git push origin` — Transfer commits in the current local branch to the + corresponding upstream branch in the remote repository. + +`git pull origin` — Bring commits from an upstream branch in the remote + repository into the current local branch. +======= +`git pull origin` — Bring commits from an upstream branch in the remote + repository into the current local branch. + +`git push origin` — Transfer commits in the current local branch to the + corresponding upstream branch in the remote repository. +>>>>>>> main +``` + +Git has injected some text into our file to describe the conflicting changes +that need to be resolved. + + +> ### Understanding the representation of conflicts +> +> The content between the markers `<<<<<<< HEAD` and `=======` contains the changes +> that were made on the current branch i.e. the changes as they are at `HEAD`. +> In contrast, the content between the markers `=======` and `>>>>>>> main` +> represents the changes that have been made on the incoming branch, which in example +> above is `main`. (Of course, in the general case the incoming branch could be a +> different branch, in which case the name of this branch will be used in the third +> marker.) + + +In order to fix this conflict, we simply need to edit this text so that it contains +only what we want to keep, just like we'd edit any other file. This gives us +complete freedom to modify the conflicted region in any way we choose. In this case, +we need to make a choice: + +* Do we stick with the version we added to our feature branch (i.e. the `HEAD` + version), where `git push` comes before `git pull`? + +* Do we go for the version that appears on the `main` branch, which has + the two commands the other way around? + +* Or do we keep some combination of the two sets of changes? + +In our example, we only want to keep one set of changes, although it's simply +a matter of taste which we go for. Let's go for the second option, i.e. the +version as it is on `main`. All we therefore need to do is: + +* Delete all the content corresponding to the `HEAD` change, i.e. delete the + content between the `<<<<<<< HEAD` and `=======` markers. + +* Delete the lines corresponding to the merge conflict markers: `<<<<<<< HEAD`, `=======` and + `>>>>>>> main`. + +Having done that, and having saved our changes to `Git-cheatsheet.md`, the +content of `Git-cheatsheet` looks like this: + +``` +## Syncing with a remote repository + +`git fetch origin` — Retrieve references to new remote branches, and/or commits + that are contained in remote branches, from the remote + repository (referred to as `origin`). + +`git push origin` — Transfer commits in the current local branch to the + corresponding upstream branch in the remote repository. + +`git pull origin` — Bring commits from an upstream branch in the remote + repository into the current local branch. + +``` + +> ### Resolving a conflict +> +> Exactly how you resolve a conflict depends on the +> context. Sometimes you will want to accept incoming changes, other times +> you'll want to keep the version on the current branch, and yet other times +> you may want to combine the changes in some way. + + +> ### Multiple conflicts in a file +> +> If a file has multiple locations where there are conflicts then each one of +> these needs to be resolved. You can find them by doing a search for +> `<<<<<<< HEAD` or similar. + + +We've now fixed our cheatsheet file to the version we'd like to keep going +forward from the merge. The next step is to stage the changes, just like +`git status` told us before: + +``` +$ git add Git-cheatsheet.md +``` + +Let's now check the status again: + +``` +$ git status +On branch pushing-pulling +Your branch is up to date with 'origin/pushing-pulling'. + +All conflicts fixed but you are still merging. + (use "git commit" to conclude merge) + +Changes to be committed: + modified: Git-cheatsheet.md + +``` + +Git is telling us that we're still in the middle of a merge but that there are +no outstanding files with conflicts left to resolve +(`All conflicts fixed but you are still merging.`) It also tells us that our +`Git-cheatsheet.md` file has changes to commit. This is as we'd expect, because +in resolving the conflict we opted for changes that differ to those we'd made +on our current branch. (If we'd instead opted to keep the version that was made +on our current branch, we wouldn't have seen any changes staged for committal.) + + +> ### Multiple conflicted files +> +> In general, you may have multiple files with conflicts when you merge. +> In this case, work through each file in turn, resolving the conflicts and staging +> the changes until all files have been addressed. + + +Since there are no more +conflicts to resolve, we can now go ahead and commit our changes to complete +the merge. + +``` +$ git commit +``` + +Note how we do this without providing a message at the command line, so that +our text editor fires up for a commit message, with the following pre-loaded +content: + +``` +Merge branch 'main' into pushing-pulling + +# Conflicts: +# Git-cheatsheet.md +# +# It looks like you may be committing a merge. +# If this is not correct, please run +# git update-ref -d MERGE_HEAD +# and try again. + + +# Please enter the commit message for your changes. Lines starting +# with '#' will be ignored, and an empty message aborts the commit. +# +# On branch pushing-pulling +# Your branch is up to date with 'origin/pushing-pulling'. +``` + +There's no problem in keeping this default message. We'll point out here that +some people like to additionally uncomment the list of conflicted files, to make +explicit that there were merge conflicts that had to be resolved. We'll just +keep the default message, saving and closing our text editor to complete the +commit: + +``` +$ git commit +[pushing-pulling c3d8b18] Merge branch 'main' into pushing-pulling +``` + +This completes the merging process. We can see the result of our merge on the +commit history of the `pushing-pulling` branch by looking at the commit graph: + +``` +$ git log --oneline --graph -4 +* c3d8b18 (HEAD -> pushing-pulling) Merge branch 'main' into pushing-pulling +|\ +| * 23e5d3a (origin/main, origin/HEAD, main) Add material about pulling and pushing branches +* | 991a78b (origin/pushing-pulling) Add entries on 'git push' and 'git pull' +|/ +* 785f6f8 Merge pull request #3 from jbloggs9999/collaboration-good-practice +|\ +``` + +Note that this graph is taken from the point of view of the `pushing-pulling` +branch, so it appears as the left-most vertical line of commits. Notice how +the merge of `main` into `pushing-pulling` is depicted at the most recent +commit. + +From here, we can complete our feature branching protocol by pushing the changes +to our remote `pushing-pulling` branch and then closing a suitable pull request +on GitHub to merge the remote `pushing-pulling` branch into `main`. If we +hadn't resolved the merge conflict locally first, then GitHub would tell us +that there are merge conflicts that need resolving before closing the pull request. +Because we've resolved the conflict locally, GitHub will happily allow the pull +request to be closed. + + +### Resolving conflicts in VS Code + +Many text editors and IDEs have capabilities that help you view and resolve +merge conflicts, or can be given these capabilities through third party extensions or plugins. +Below we look at how the merge conflict in the previous section could have been +resolved using the VS Code editor. + +In the screenshot below, notice first how the left-hand _Explorer_ pane has +the `Git-cheatsheet.md` file highlighted in red, with an exclamation mark next to it. +This is how VS Code marks files that have conflicts in them. Looking now at +the open `Git-cheatsheet.md` file, we see that VS Code has highlighted the places +where there is a conflict to resolve. + +![A merge conflict in VS Code]({{ site.url }}/images/vs-code-merge-conflict.png) + +In addition to this, you can jump between unresolved conflicts using the arrows +in the top right-hand corner of the editor pane: + +![Navigating conflicts in VS Code]({{ site.url }}/images/vs-code-conflict-navigation.png) + +To support the resolution of the conflicts themselves, VS Code provides some +shortcut options above the conflict. These represent methods for resolution that +apply in the majority of cases in practice: + +* _Accept Current Change_: Use the change arising from the current branch, + i.e. as at `HEAD`. + +* _Accept Incoming Change_: Use the change from the incoming branch. + +* _Accept Both Changes_: Include both changes, in the order they appear. + +* _Compare Changes_: View the differences between the changes in a side-by-side + view. + +![Automatic options for resolving a conflict in VS Code]({{ site.url }}/images/vs-code-select-change.png) + +Clicking on one of these options will apply the specified resolution, ready to +be saved. (The screenshot below shows the result from applying the +_Accept Incoming Change_ option.) + +![A resolved merge conflict in VS Code]({{ site.url }}/images/vs-code-conflict-resolution.png) + + +## General advice on merge conflicts and collaboration + +There's no silver bullet, or special branching strategy, for avoiding merge +conflicts. _The only way to avoid conflicts is to communicate with each other._ + +You want to avoid the situation where two people work on the same part of a file +in different branches, since this will cause a conflict when merging +the branches back into `main`. + +The best way to avoid conflicts is to ensure different branches work on +different files as much as possible. Admittedly, this is not always possible or +practical. If you need to work on a file which may also be in the process of being +edited in another branch, then you should flag this with collaborators +and agree a way forward which integrates both sets of changes. + +It's also good practice to make sure everyone is aware when a feature branch +has been merged into `main` on the remote repository. This ensures everyone can +update their local repositories and make sure their work builds on top of the +latest version of the common codebase. GitHub can be set up so that collaborators +on a repository are notified when a new pull request is closed. + +Finally, if you get a conflict when trying to merge `main` into a feature branch +and the incoming changes affect the correctness of what you're doing, then +have a call with the person whose work has caused the conflict, to discuss the +problem and agree how you will resolve the conflict to move forward. diff --git a/_episodes/12_wrapup.md b/_episodes/17_wrapup.md similarity index 99% rename from _episodes/12_wrapup.md rename to _episodes/17_wrapup.md index 9129459..07155ae 100644 --- a/_episodes/12_wrapup.md +++ b/_episodes/17_wrapup.md @@ -1,7 +1,7 @@ --- layout: page title: Wrap up -order: 12 +order: 17 session: 2 length: 5 toc: true