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.
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
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.
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.
You can download the full source code of this example here: