14. Manage Differences in Files

Typically, developers love writing code. Unfortunately, writing code is not the only responsibility of software developers. Searching for differences between two versions of source code files and merging them into a new version is a major part of writing code.

The Git software tries to make this process easier by providing you with tools that display the difference between files and help you merge files together. These tools are the focus of this chapter.

Executing Diffs

You arrive at work Monday morning, ready to start on your project. Executing the git status command when you haven't worked on the project for a while is always a good habit, so you execute the command and discover that you have a file in the working area that hasn't been staged:

ocs@ubuntu:~/ocs$ git status
On branch master
Your branch is up-to-date with 'origin/master'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

       modified:   hidden.sh

no changes added to commit (use "git add" and/or "git commit -a")

You could just stage and commit the file, but you wonder what changes were made to this file. Maybe you didn't finish making the changes last Friday. This might pose problems, so comparing the version of the file in the working directory with the version that has most recently been committed to the local repository is best. You can do this by executing the git diff command:

ocs@ubuntu:~/ocs$ git diff hidden.sh | cat -n
     1  diff --git a/hidden.sh b/hidden.sh
     2  index 05151ce..714482b 100644
     3  --- a/hidden.sh
     4  +++ b/hidden.sh
     5  @@ -1,4 +1,5 @@
     6   #!/bin/bash
     7   #hidden.sh
     8
     9  +echo "Hidden files:"
    10   ls -ld .* $1

The output of this command requires a bit of explaining.1 Ignore the first two lines of output because they aren't important at this point.

Lines 3 and 4 refer to the two versions of the file. Each version is assigned a letter (a or b) to distinguish between the two. Line 3 refers to the version that has been committed, whereas line 4 refers to the version in the working directory.

Line 5 gives “directions” on how to make the two files look the same. In this case, it is simply saying “at line 4 of the 'a' file add line 5 of the 'b' file.” Following these directions would make the files look the same by making the committed version look like the version in the working directory.

Lines 6–10 visually show what changes would have to take place in the committed version to make it look like the version in the working directory. A + before a line means "add this" and a – before a line means "remove this."


Note

The output of the git diff command is referred to as patch output. You can use this output to patch a file (essentially, upgrade the file to the most current version). Chapter 15, “Advanced Git Features” covers this process.


Understanding that this comparison is performed on a line-by-line basis is important. A single change on one line would mean the lines are completely different to the git diff command. For example, look at Listing 14.1 and notice that the only difference between lines 9 and 11 is a single character.


Listing 14.1 A single difference

ocs@ubuntu:~/ocs$ git diff hidden.sh | cat -n
     1  diff --git a/hidden.sh b/hidden.sh
     2  index 05151ce..6de92ae 100644
     3  --- a/hidden.sh
     4  +++ b/hidden.sh
     5  @@ -1,4 +1,5 @@
     6   #!/bin/bash
     7   #hidden.sh
     8
     9  -ls -ld .* $1
    10  +echo "Hidden files:"
    11  +ls -ldh .* $1


By default, the git diff command compares versions in the following two situations:

If the working directory version is different than the committed version, but only if the working version hasn't been staged

If the working directory version is different than the staged version

That means if you stage a file, the git diff command won't compare the staged version to the committed version, at least not by default. As you can see, there is no output for the following git diff command:

ocs@ubuntu:~/ocs$ git add hidden.sh
ocs@ubuntu:~/ocs$ git diff | cat -n

To compare a staged version to a committed version, use the --staged option as shown in Listing 14.2.


Listing 14.2 Staged versus committed difference

ocs@ubuntu:~/ocs$ git diff --staged hidden.sh | cat -n
     1  diff --git a/hidden.sh b/hidden.sh
     2  index 05151ce..6de92ae 100644
     3  --- a/hidden.sh
     4  +++ b/hidden.sh
     5  @@ -1,4 +1,5 @@
     6   #!/bin/bash
     7   #hidden.sh
     8
     9  -ls -ld .* $1
    10  +echo "Hidden files:"
    11  +ls -ldh .* $1


Dealing with White Space

A useful option to the git diff command is the --check option, which looks for white spaces. To understand the importance of this, first look at the output of Listing 14.3.


Listing 14.3 White space mystery

ocs@ubuntu:~/ocs$ git diff --staged hidden.sh | cat -n
     1  diff --git a/hidden.sh b/hidden.sh
     2  index 6de92ae..519eb3c 100644
     3  --- a/hidden.sh
     4  +++ b/hidden.sh
     5  @@ -1,5 +1,5 @@
     6   #!/bin/bash
     7  -#hidden.sh
     8  +#hidden.sh
     9
    10   echo "Hidden files:"
    11   ls -ldh .* $1


Based on the output of Listing 14.3, lines 7 and 8 are different. However, they look exactly the same. To see why they are different use the --check option:

ocs@ubuntu:~/ocs$ git diff --staged --check | cat -n
     1  hidden.sh:2: trailing whitespace.
     2  +#hidden.sh

The message trailing whitespace means some sort of white space characters (spaces, tabs, and so on) are at the end of the line.2

Comparing Branches

You can also use the git diff command to compare files in different branches. For example, to see a list of files that are different between two branches, use the following command:

ocs@ubuntu:~/ocs$ git diff --name-status master..test
M       hidden.sh

In the previous git diff command, the --name-status option provides a summary of the files that are different in the two branches. The two branches, master and test, are listed, separated by .. characters.

To see the differences between the versions in the two branches, use the syntax shown in Listing 14.4.


Listing 14.4 git diff between branches

ocs@ubuntu:~/ocs$ git diff master:hidden.sh test:hidden.sh
diff --git a/master:hidden.sh b/test:hidden.sh
index 519eb3c..804fcf7 100644
--- a/master:hidden.sh
+++ b/test:hidden.sh
@@ -1,5 +1,5 @@
#!/bin/bash
-#hidden.sh
+#hidden.sh

-echo "Hidden files:"
-ls -ldh .* $1
+echo "Listing only hidden files:"
+ls -ld .* $1


If you find the output of the git diff command to be confusing, consider using the git difftool command:3

ocs@ubuntu:~/ocs$ git difftool hidden.sh

This message is displayed because 'diff.tool' is not configured.
See 'git difftool --tool-help' or 'git help config' for more details.
'git difftool' will now attempt to use one of the following tools:
opendiff kdiff3 tkdiff xxdiff meld kompare gvimdiff diffuse diffmerge ecmerge p4merge araxis bc3 codecompare emerge vimdiff

Viewing (1/1): 'hidden.sh'
Launch 'vimdiff' [Y/n]: y
2 files to edit

Note how it prompts you for the tool to use to display the differences. It also lists the tools that could be available.4 If you want to use a different tool than the one it chooses, then execute the command as shown in the following:

git difftool --tool=<tool> file

The output of git difftool appears in a more readable format. For example, see Figure 14.1 for the output of git difftool when you use the vimdiff command as the display tool.5

Figure 14.1 git difftool using vimdiff

Merging Files

Suppose you create a new branch to add a new feature to a file as demonstrated in Listing 14.5.


Listing 14.5 Features branch

ocs@ubuntu:~/ocs$ more showmine.sh
#!/bin/bash
#showmine.sh

echo "Your processes:"
ps -fe | grep $USER | more
ocs@ubuntu:~/ocs$ git checkout -b feature127
Switched to a new branch 'feature127'
ocs@ubuntu:~/ocs$ vi showmine.sh
ocs@ubuntu:~/ocs$ more showmine.sh
#!/bin/bash
#showmine.sh

echo -n "Enter name username or press enter: "
read person

echo "${person:-$USER} processes:"
ps -fe | grep "^${person:-$USER}" | more


After testing out this new feature (look at the last two lines of the output of Listing 14.5 to see the new feature), you are ready to implement it in the master branch. To do this, you will need to merge the content from the feature127 branch into the master branch. Start by committing all changes in the feature127 branch and then switch back to the master branch:

ocs@ubuntu:~/ocs$ git commit -a -m "feature added to showmine.sh"
[feature127 2e5defa] feature added to showmine.sh
 1 file changed, 5 insertions(+), 2 deletions(-)
ocs@ubuntu:~/ocs$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 3 commits.
  (use "git push" to publish your local commits)

You must be in the branch that you want to merge into in order to correctly run the next command. The following git merge command merges the changes from the feature127 branch into the master branch:

ocs@ubuntu:~/ocs$ git merge feature127
Updating 4810ca8..2e5defa
Fast-forward
 showmine.sh | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

After the feature has been implemented, you may decide that the feature127 branch is no longer necessary. To remove this branch, execute the following command:

ocs@ubuntu:~/ocs$ git branch -d feature127
Deleted branch feature127 (was 2e5defa).

This merge process can be more complex. For example, there was a separate branch named test that was branched off an earlier version of the master branch. In the test branch, the most recent showmine.sh script looks like the following:

ocs@ubuntu:~/ocs$ git checkout test
ocs@ubuntu:~/ocs$ more showmine.sh
#!/bin/bash
#showmine.sh

echo "Your programs:"
ps -fe | grep $USER | more

echo -n "Enter a PID to stop: "
read proc
kill $proc

The current version of showmine.sh that has been committed to the master branch looks like the following:

ocs@ubuntu:~/ocs$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 4 commits.
  (use "git push" to publish your local commits)
ocs@ubuntu:~/ocs$ more showmine.sh
#!/bin/bash
#showmine.sh

echo -n "Enter name username or press enter: "
read person

echo "${person:-$USER} processes:"
ps -fe | grep "^${person:-$USER}" | more

Should you merge the changes from the master into the test branch? Or should you merge the changes from the test branch into the master branch? Typically, if you have more work to do in the test branch, merge the changes from the master into the test branch. Otherwise, merge the changes from the test branch into the master branch.

The following example merges the changes from the master branch into the test branch:

ocs@ubuntu:~/ocs$ git checkout test
Switched to branch 'test'
Your branch is ahead of 'origin/test' by 1 commit.
  (use "git push" to publish your local commits)
ocs@ubuntu:~/ocs$ git merge master
Auto-merging showmine.sh
CONFLICT (content): Merge conflict in showmine.sh
Auto-merging hidden.sh
CONFLICT (content): Merge conflict in hidden.sh
Automatic merge failed; fix conflicts and then commit the result.

You can see that the merge was not completed because the automated merge process ran into some conflicts. You can also see these conflicts by executing the git status command:

ocs@ubuntu:~/ocs$ git status
On branch test
Your branch is ahead of 'origin/test' by 1 commit.
  (use "git push" to publish your local commits)

You have unmerged paths.
  (fix conflicts and run "git commit")

Unmerged paths:
  (use "git add <file>..." to mark resolution)

        both modified:      hidden.sh
        both modified:      showmine.sh

no changes added to commit (use "git add" and/or "git commit -a")

This makes it clearer that two files have conflicts, not just one. If you look at the new showmine.sh file in the working directory, it will look something like Listing 14.6.


Listing 14.6 Merged file

ocs@ubuntu:~/ocs$ cat -n showmine.sh
     1  #!/bin/bash
     2  #showmine.sh
     3
     4  <<<<<<< HEAD
     5  echo "Your programs:"
     6  ps -fe | grep $USER | more
     7
     8  echo -n "Enter a PID to stop: "
     9  read proc
    10  kill $proc
    11
    12  =======
    13  echo -n "Enter name username or press enter: "
    14  read person
    15
    16  echo "${person:-$USER} processes:"
    17  ps -fe | grep "^${person:-$USER}" | more
    18  >>>>>>> master


Essentially, the file contains the contents of each file. Rather than try to edit this file directly, one way to handle these conflicts is to use the git mergetool command:6

ocs@ubuntu:~/ocs$ git mergetool showmine.sh

This message is displayed because 'merge.tool' is not configured.
See 'git mergetool --tool-help' or 'git help config' for more details.
'git mergetool' will now attempt to use one of the following tools:
opendiff kdiff3 tkdiff xxdiff meld tortoisemerge gvimdiff diffuse diffmerge ecmerge p4merge araxis bc3 codecompare emerge vimdiff
Merging:
showmine.sh

Normal merge conflict for 'showmine.sh':
  {local}: modified file
  {remote}: modified file
Hit return to start merge resolution tool (vimdiff):

The git mergetool command displays the files using one of the diff tools. For example, using the vimdiff tool displays as shown in Figure 14.2.7

Figure 14.2 git mergetool using vimdiff

The first time you see this output, you might be a bit overwhelmed. It isn't as bad as you might think. To see how this works, look at Figure 14.3.

Figure 14.3 Understanding vimdiff

BASE is what the file looked like when the two branches were last in sync. LOCAL represents how the file looks in the current branch (the test branch in this example). REMOTE represents how the file looks like in the branch that is being merged into this branch (the master branch in this example). The file at the bottom is the file that you are creating/merging.

As you can see from Figure 14.2, the first three lines of all versions of the file are identical. If you go to the first line that differs between the LOCAL and REMOTE, you can execute the :diffget RE command to grab the code from the REMOTE file. This would grab the echo, read, echo, and ps commands in this example.

Suppose you want to input specific lines from one of the three files? For example, you want to copy the last four lines of the LOCAL version into the merged area. In this case you essentially do a copy and paste operation.

To switch to the LOCAL window, hold down the Ctrl button and then press the W key twice (Ctrl+W+W). Then copy the four lines (go to the first line that you want to copy then type 4yy). Use Ctrl+W+W to move forward until the cursor returns to the merged copy. Then move to where you want to make the change and press the P key.

After making all of your changes, you need to save and quit all four versions. The easiest way to do this is the :wqa vim command.

Merge all files and then stage/commit them with the git commit -a command.8


Git Humor

"Git knows what you did last summer!"

—Anonymous


Summary

At this point you should now know how to create branches, see the difference in versions of files, and merge files from one branch to another.

1 If you run this on your own system and don't pipe the output to the cat command, you should see some color highlight that helps you understand the data.

2 Note that I executed git commit at this point.

3 Note that you need to install the git-all package if you want to use the git difftool command.

4 Most likely not all of these tools are installed on your system.

5 Note that I executed git commit at this point.

6 Note that you need to install the git-all package if you want to use the git mergetool command.

7 The vimdiff utility highlights differences using colors that are sure to give you a headache if you look at them too long. I highly suggest executing the :diffoff! command immediately to avoid visual problems and dizziness. However, when you want to make changes to the file, you need to turn this feature back on by executing the :window diffthis command.

8 If you are following along, be aware that at the end of this chapter I completed the merge of all files (including the hidden.sh file), committed all changes to the test branch, and pushed all changes for both the test and master branch to the central repository server.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.225.149.32