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. 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