Git

Git diff between Branches Example

In this post, we feature a comprehensive Example on Git diff between Branches.

1. Introduction

Version control system (VCS) software is designed to track and manage changes in a file repository. Branching is a common operation performed with any VCS. It allows a developer (or team of developers) to work on code or documents in a repository without affecting the mainline, which is used for deploying production-ready code.

There are different reasons for creating a branch. You may want to develop a new feature or produce a hotfix for a critical defect. In each of these cases, it is essential that changes are made in a branch and the code tested before merging it back to the mainline and pushing it to production.

Git is a popular VCS. One feature that makes Git standout among other VCSs is its branching mechanism. The mechanism used by Git to create and manage branches is both lightweight and efficient in comparison to other VCSs.

1.1 Tools Used in this Example

  • Git 2.17
  • Maven 3.5.4
  • DiffMerge 4.2

Git downloads are available here: https://git-scm.com/downloads.

Maven downloads are available here: https://maven.apache.org/download.cgi.
Instructions for installing Maven are provided here: https://maven.apache.org/install.html.

DiffMerge downloads are available here: https://sourcegear.com/diffmerge/downloads.php.

Note: This example was created on the macOS platform. Git for Windows includes Git Bash and Git CMD shells to run command-line operations.

2. Git diff between Branches Example

In this example, we will create a branch based on the mainline (or ‘master’ branch) of a Git repository. We will then make changes in the branch and use the Git ‘diff’ operation to compare the two.

2.1 Download and Extract the Sample Project

First, download the “REST API initial” archive from the Download section and extract it in an empty directory of your choice.

Git diff between Branches - REST API Project
REST API Project

2.2 Create a Local Repository

Open a terminal (shell) in the directory where the archive was extracted and run the following command:

$ git init

Git init Command Output

Initialized empty Git repository in /Users/gilbertlopez/gitdiffexample/.git/

This creates an empty Git repository. The repository metadata has been generated in the “.git” folder.

(Note: This folder is hidden by default as you would, typically, not edit its contents.)

Next, check the status with the following command:

$ git status

Git status Command Output

On branch master

Initial commit

Untracked files:
  (use "git add ..." to include in what will be committed)

	REST-API/

nothing added to commit but untracked files present (use "git add" to track) 

Notice the following two things:

  • We are currently on branch “master” (the mainline or root trunk).
  • The “REST-API” project is in the directory but will not be tracked until it is added to the repository.

2.3 Add and Commit the Project to the Repository

Let’s add the project to the repository. Run the following command (don’t forget the dot at the end):

$ git add .

This will add all the files and folders of the current directory, recursively, to the repository index. The project files and folders are now in the staging area. You can run the git status command to see the changes that are to be committed.

Git status Command Output

$ git status
On branch master

Initial commit

Changes to be committed:
  (use "git rm --cached ..." to unstage)

	        new file:   REST-API/.gitignore
        new file:   REST-API/.mvn/wrapper/maven-wrapper.jar
        new file:   REST-API/.mvn/wrapper/maven-wrapper.properties
        new file:   REST-API/mvnw
        new file:   REST-API/mvnw.cmd
        new file:   REST-API/pom.xml
        new file:   REST-API/src/main/java/com/javacodegeeks/example/RestApiAppl                           ication.java
        new file:   REST-API/src/main/java/com/javacodegeeks/example/controller/                           StudentController.java
        new file:   REST-API/src/main/java/com/javacodegeeks/example/model/Stude                           nt.java
        new file:   REST-API/src/main/java/com/javacodegeeks/example/repository/                           StudentRepository.java
        new file:   REST-API/src/main/resources/application.properties
        new file:   REST-API/src/main/resources/eyre.json
        new file:   REST-API/src/main/resources/gates.json
        new file:   REST-API/src/test/java/com/javacodegeeks/example/RestApiAppl                           icationTests.java

To commit the files from the staging area to the repository, run the following command:

$ git commit -m 'Initial commit of project'

Note: The -m option allows us to add a commit message inline.

Git commit Command Output

master (root-commit) e682818] initial commit of project
 14 files changed, 667 insertions(+)
 create mode 100755 REST-API/.gitignore
 create mode 100755 REST-API/.mvn/wrapper/maven-wrapper.jar
 create mode 100755 REST-API/.mvn/wrapper/maven-wrapper.properties
 create mode 100755 REST-API/mvnw
 create mode 100755 REST-API/mvnw.cmd
 create mode 100755 REST-API/pom.xml
 create mode 100755 REST-API/src/main/java/com/javacodegeeks/example/RestApiApplication.java
 create mode 100755 REST-API/src/main/java/com/javacodegeeks/example/controller/StudentController.java
 create mode 100755 REST-API/src/main/java/com/javacodegeeks/example/model/Student.java
 create mode 100755 REST-API/src/main/java/com/javacodegeeks/example/repository/StudentRepository.java
 create mode 100755 REST-API/src/main/resources/application.properties
 create mode 100755 REST-API/src/main/resources/eyre.json
 create mode 100755 REST-API/src/main/resources/gates.json
 create mode 100755 REST-API/src/test/java/com/javacodegeeks/example/RestApiApplicationTests.java

2.4 Create a New Branch

Our application is a REST API student management service that allows clients to read, add, and update students.

Let us imagine that there is a request to implement a new feature that gives the consumer of our API the ability to delete a student from the datastore. The first thing we should do is create a branch. Create a new branch named ‘Feaure1’ with the following Git command:

$ git branch Feature1

Now run the following command:

$ git branch

Git branch Command Output

  Feature1
* master

As you can see from the output, there are two branches now. The * next to master tells us that we are in the master branch (the mainline). Let’s switch to the Feature1 branch so we can work on the new feature.

2.5 Switch to the Feature1 Branch

To switch to the Feature1 branch, run the following command:

$ git checkout Feature1

Git checkout Command Output

Switched to branch 'Feature1'

Note: To create and switch to the branch using a single command, you can use the -b option with the checkout command. For example:

$ git checkout -b Feature1

Now we can happily work on the new feature without affecting the master branch.

2.6 Implement the New Feature

Add the following method to StudentRepository.java in the com.javacodegeeks.example.repository package and save the changes.

StudentRepository.java

package com.javacodegeeks.example.repository;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

import org.springframework.stereotype.Repository;

import com.javacodegeeks.example.model.Student;

@Repository
public class StudentRepository {
	
	Map students = new HashMap();
	long currentId = 100;
	
	// Return all students
	public Collection findAll(){
		return students.values();
	}

	// Find the student with this id
	public Optional findById(Long id) {
		Student student = null;

		if (students.containsKey(id)) student = students.get(id);
		return Optional.ofNullable(student);
	}
		
	// Save a new student	
	public Student save(Student student) {
		student.setId(++currentId);
		students.put(student.getId(), student);
		return student;
	}
	
	// Update the student with this id
	public Optional update(Student student) {
		Student currentStudent = students.get(student.getId());

		if (currentStudent != null) {
			students.put(student.getId(), student);
			currentStudent = students.get(student.getId());
		}
		return Optional.ofNullable(currentStudent);
	}

	// Feature 1: Delete student with this id
	public Optional delete(Long id) {
		Student currentStudent = students.get(id);

		if (currentStudent != null) {
			students.remove(id);
		}
		return Optional.ofNullable(currentStudent);
	}	

}

Now add the following import statement and method to StudentController.java in the com.javacodegeeks.example.controller package and save the changes.

StudentController.java

package com.javacodegeeks.example.controller;

import java.net.URI;
import java.util.Collection;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import com.javacodegeeks.example.model.Student;
import com.javacodegeeks.example.repository.StudentRepository;

@RestController
@RequestMapping("/students")
public class StudentController {

	private final StudentRepository repository;

	@Autowired
	public StudentController(StudentRepository repository) {
		this.repository = repository;
	}
	
	@SuppressWarnings("serial")
	@ResponseStatus(HttpStatus.NOT_FOUND)
	class StudentNotFoundException extends RuntimeException {

		public StudentNotFoundException() {
			super("Student does not exist");
		}
	}
	
	@GetMapping
	Collection readStudents(){
		return this.repository.findAll();
	}
	
	@GetMapping("/{id}")
	Student readStudent(@PathVariable Long id) {
		return this.repository.findById(id)
				.orElseThrow(StudentNotFoundException::new);
	}
	
	@PostMapping
	ResponseEntity addStudent(@RequestBody Student student){
		Student result = this.repository.save(student);
		URI location = ServletUriComponentsBuilder
				.fromCurrentRequest()
				.path("/{id}")
				.buildAndExpand(result.getId())
				.toUri();

		return ResponseEntity.created(location).build();		
	}
	
	@PutMapping
	Student updateStudent(@RequestBody Student student) {
		return this.repository.update(student)
				.orElseThrow(StudentNotFoundException::new);
	}

	// Feature 1: Delete student with this id
	@DeleteMapping("/{id}")
	void deleteStudent(@PathVariable Long id) {
		this.repository.delete(id)
			.orElseThrow(StudentNotFoundException::new);
	}

}

2.7 Test the Application

Navigate to the REST-API directory and run the following command:

$ mvn spring-boot:run

Maven spring-boot Run Command Output

[INFO] Scanning for projects...
[INFO] 
[INFO] ------------------------------------------
[INFO] Building REST-API 0.0.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] >>> spring-boot-maven-plugin:2.0.4.RELEASE:run (default-cli) > test-compile @ REST-API >>>
[INFO] 
[INFO] --- maven-resources-plugin:3.0.2:resources (default-resources) @ REST-API ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO] Copying 2 resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.7.0:compile (default-compile) @ REST-API ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 4 source files to /Users/gilbertlopez/gitdiffexample/REST-API/target/classes
[INFO] 
[INFO] --- maven-resources-plugin:3.0.2:testResources (default-testResources) @ REST-API ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /Users/gilbertlopez/gitdiffexample/REST-API/src/test/resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.7.0:testCompile (default-testCompile) @ REST-API ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to /Users/gilbertlopez/gitdiffexample/REST-API/target/test-classes
[INFO] 
[INFO] <<< spring-boot-maven-plugin:2.0.4.RELEASE:run (default-cli) < test-compile @ REST-API <<<
[INFO] 
[INFO] 
[INFO] --- spring-boot-maven-plugin:2.0.4.RELEASE:run (default-cli) @ REST-API ---

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.0.4.RELEASE)

2018-08-31 16:15:05.696  INFO 1216 --- [           main] c.j.example.RestApiApplication           : Starting RestApiApplication on Gilberts-MBP.attlocal.net with PID 1216 (/Users/gilbertlopez/gitdiffexample/REST-API/target/classes started by gilbertlopez in /Users/gilbertlopez/gitdiffexample/REST-API)
2018-08-31 16:15:05.702  INFO 1216 --- [           main] c.j.example.RestApiApplication           : No active profile set, falling back to default profiles: default
2018-08-31 16:15:05.817  INFO 1216 --- [           main] ConfigServletWebServerApplicationContext : Refreshing org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@6caaa61b: startup date [Fri Aug 31 16:15:05 PDT 2018]; root of context hierarchy
2018-08-31 16:15:08.104  INFO 1216 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2018-08-31 16:15:08.161  INFO 1216 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2018-08-31 16:15:08.162  INFO 1216 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/8.5.32
2018-08-31 16:15:08.184  INFO 1216 --- [ost-startStop-1] o.a.catalina.core.AprLifecycleListener   : The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: [/Users/gilbertlopez/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:.]
2018-08-31 16:15:08.329  INFO 1216 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2018-08-31 16:15:08.329  INFO 1216 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 2518 ms
2018-08-31 16:15:08.427  INFO 1216 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean  : Servlet dispatcherServlet mapped to [/]
2018-08-31 16:15:08.433  INFO 1216 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'characterEncodingFilter' to: [/*]
2018-08-31 16:15:08.433  INFO 1216 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2018-08-31 16:15:08.433  INFO 1216 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2018-08-31 16:15:08.434  INFO 1216 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'requestContextFilter' to: [/*]
2018-08-31 16:15:08.666  INFO 1216 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-08-31 16:15:09.016  INFO 1216 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@6caaa61b: startup date [Fri Aug 31 16:15:05 PDT 2018]; root of context hierarchy
2018-08-31 16:15:09.187  INFO 1216 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/students],methods=[GET]}" onto java.util.Collection com.javacodegeeks.example.controller.StudentController.readStudents()
2018-08-31 16:15:09.189  INFO 1216 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/students/{id}],methods=[GET]}" onto com.javacodegeeks.example.model.Student com.javacodegeeks.example.controller.StudentController.readStudent(java.lang.Long)
2018-08-31 16:15:09.190  INFO 1216 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/students],methods=[POST]}" onto org.springframework.http.ResponseEntity com.javacodegeeks.example.controller.StudentController.addStudent(com.javacodegeeks.example.model.Student)
2018-08-31 16:15:09.191  INFO 1216 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/students],methods=[PUT]}" onto com.javacodegeeks.example.model.Student com.javacodegeeks.example.controller.StudentController.updateStudent(com.javacodegeeks.example.model.Student)
2018-08-31 16:15:09.191  INFO 1216 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/students/{id}],methods=[DELETE]}" onto void com.javacodegeeks.example.controller.StudentController.deleteStudent(java.lang.Long)
2018-08-31 16:15:09.195  INFO 1216 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map> org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2018-08-31 16:15:09.198  INFO 1216 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2018-08-31 16:15:09.248  INFO 1216 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-08-31 16:15:09.248  INFO 1216 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-08-31 16:15:09.603  INFO 1216 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2018-08-31 16:15:09.691  INFO 1216 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2018-08-31 16:15:09.698  INFO 1216 --- [           main] c.j.example.RestApiApplication           : Started RestApiApplication in 4.747 seconds (JVM running for 11.57)

This will compile and start the application. You can now test the new feature. (For instructions on testing this application, please visit: Spring Boot REST API Tutorial.)

2.8 Commit Changes to New Branch

Once your test cases have passed, it is time to commit the changes to the feature branch. Navigate to the parent directory of REST-API and run the following command.

$ git commit -a -m 'Feature 1: Delete student'

The -a option lets us skip the git add command and the -m option allows us to add a commit message inline.

Git commit Command Output

[Feature1 7878c76]  Feature 1: Delete student
 2 files changed, 19 insertions(+), 1 deletion(-)

At this point, the ‘Feature1’ branch and the ‘master’ branch are pointing to different commits

Git diff between Branches - Two Branches Pointing to Different Commits
Two Branches Pointing to Different Commits

Let’s compare the branches.

2.7 Compare the Branches using Git diff

There are different compare tools available for viewing differences between file versions in a git repository, or for that matter, file differences between branches. The Git distribution includes the diff command.

Let’s compare the feature1 branch against the master branch. Run the following command:

$ git diff master

Git diff Command Output

diff --git a/REST-API/src/main/java/com/javacodegeeks/example/controller/StudentController.java b/REST-API/src/main/java/com/javacodegeeks/example/controller/StudentController.java
index dc28c31..a07e95e 100755
--- a/REST-API/src/main/java/com/javacodegeeks/example/controller/StudentController.java
+++ b/REST-API/src/main/java/com/javacodegeeks/example/controller/StudentController.java
@@ -5,7 +5,8 @@ import java.util.Collection;
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;
-import org.springframework.http.ResponseEntity;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.DeleteMapping;^M
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.PostMapping;
@@ -66,6 +67,13 @@ public class StudentController {
        Student updateStudent(@RequestBody Student student) {
                return this.repository.update(student)
                                .orElseThrow(StudentNotFoundException::new);
+       }
+
+       // Feature 1: Delete student with this id
+       @DeleteMapping("/{id}")
+       void deleteStudent(@PathVariable Long id) {
+               this.repository.delete(id)
+                       .orElseThrow(StudentNotFoundException::new);
        }
 
 }

Press the space bar to see the other change we committed.

Git diff Command Output

diff --git a/REST-API/src/main/java/com/javacodegeeks/example/repository/StudentRepository.java b/REST-API/src/main/java/com/javacodegeeks/example/repository/StudentRepository.java
index 9a9bdc7..76615f6 100755
--- a/REST-API/src/main/java/com/javacodegeeks/example/repository/StudentRepository.java
+++ b/REST-API/src/main/java/com/javacodegeeks/example/repository/StudentRepository.java
@@ -44,6 +44,16 @@ public class StudentRepository {
                        currentStudent = students.get(student.getId());
                }
                return Optional.ofNullable(currentStudent);
+       }
+
+       // Feature 1: Delete student with this id
+       public Optional delete(Long id) {
+               Student currentStudent = students.get(id);
+
+               if (currentStudent != null) {
+                       students.remove(id);
+               }
+               return Optional.ofNullable(currentStudent);
        }
 
 }

Hit q to quit. On Windows, you may need to hit Control + \ to quit.

If you have a GUI tool such as DiffMerge or Beyond Compare installed on your system, you can configure Git to use it with the config command. For example, run the following command to have Git use DiffMerge as your diff tool:

$ git config --global diff.tool diffmerge

Now you can run the Git difftool command.

$ git difftool master

Git difftool Command Output

Viewing (1/2): 'REST-API/src/main/java/com/javacodegeeks/example/controller/StudentController.java'
Launch 'diffmerge' [Y/n]? 

Enter ‘y’ at the prompt and hit enter. This will launch DiffMerge.

Git diff between Branches - DiffMerge
DiffMerge


3. Summary

In this post, we demonstrated how to create a branch in Git and showed how to compare the branch against the mainline using Git’s diff command.


4. Download the Source Code

That was Git diff between Branches Example.

Download
You can download the full source code of this example here:

Gilbert Lopez

Gilbert Lopez is an application developer and systems integration developer with experience building business solutions for large and medium-sized companies. He has worked on many Java EE projects. His roles have included lead developer, systems analyst, business analyst and consultant. Gilbert graduated from California State University in Los Angeles with a Bachelor of Science degree in Business.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button