Git Lifecycle: Simple Merging and Resolving

Merging becomes necessary when you're working in one branch and you wish to pull work done from or push new work of your own to another branch. The important Git commands involved are:

Practical concepts

Each branch has two parts: the local and the remote. The local one resides on your development host. The remote one is (usually) a copy of the branch, but at the back-end server. If you juggle, as I do, two repositories, like jack and master, you have in effect four places to worry about.

When you wish to save your changes to the remote copy of your branch, which is probably the one that's backed up with some reliability, you must simply do a git push. Git knows that this means git push origin. This will mean you've got your new functionality in the local and remote copies.

$ git push Counting objects: 31, done. Delta compression using up to 4 threads. Compressing objects: 100% (17/17), done. Writing objects: 100% (18/18), 3.57 KiB, done. Total 18 (delta 7), reused 0 (delta 0) To [email protected]:acme-server d117cb2..4d45ed7 jack -> jack

However, maybe the protocol is, for a piece of finished functionality, to commit it to the master repository. To do this, you need only checkout master and do a merge. If you've been following along here, doing this will result in your new code being in the local and remote branches of jack and in the local master repository.

$ git checkout master Switched to branch 'master' $ git branch * master jack $ git merge jack Merge made by recursive. extras/schema/postgres/debitcard-table.sql | 12 +++ extras/schema/postgres/payment-table.sql | 17 +++-- extras/schema/postgres/paypalpayment-table.sql | 13 ++++ extras/schema/postgres/schema.sql | 4 +- src/com/acme/web/user/entity/DebitCard.java | 83 +++++++++++++++++++++ src/com/acme/web/user/entity/PayPalPayment.java | 92 ++++++++++++++++++++++++ src/com/acme/web/user/entity/Payment.java | 12 ++-- 7 files changed, 220 insertions(+), 13 deletions(-) create mode 100644 extras/schema/postgres/debitcard-table.sql create mode 100644 extras/schema/postgres/paypalpayment-table.sql create mode 100644 src/com/acme/web/user/entity/DebitCard.java create mode 100644 src/com/acme/web/user/entity/PayPalPayment.java

Finally, if you wish what's in your copy of master to make it into the remote copy, what everyone else on the team is cloning, pulling, etc., you must push back to the remote:

$ git push Counting objects: 22, done. Delta compression using up to 4 threads. Compressing objects: 100% (7/7), done. Writing objects: 100% (8/8), 801 bytes, done. Total 8 (delta 2), reused 0 (delta 0) To [email protected]:acme-server 4b85a48..c1abe51 master -> master

Continuing on: merging

Of course, all the above worked with remarkable straight-forwardness, all with "fast-forward" commits. What if there are conflicts?

First, you may not even get what's on origin master. Let's say you're working in master already, have made some changes, but hear that someone else has pushed import stuff you need to origin master. You want to pull it down.

master ~/acme $ git pull origin master remote: Counting objects: 263, done. remote: Compressing objects: 100% (146/146), done. remote: Total 263 (delta 53), reused 215 (delta 50) Receiving objects: 100% (263/263), 210.33 KiB, done. Resolving deltas: 100% (53/53), done. From github.com:acme * branch master -> FETCH_HEAD Updating 8f67766..590bc02 error: Your local changes to the following files would be overwritten by merge: acme/src/com/acme/sources/SourceReferenceService.java Please, commit your changes or stash them before you can merge. Aborting

This just means that you've made changes already to SourceReferenceService.java and that if the file on origin master were to be brought down, it would clobber those changes.

The solution is to commit your changes and try again. At that point and if the changes are interleaved, Git will merge your changes with those on origin master using the

<<<<<<< HEAD text ======= different text >>>>>>> jack

...format and you'll have the opportunity to finish the merge by hand.

Or, imagine you have a branch, jack. You've created it from master and make one change to a file in it that you wish to push back to master. You add and commit the change:

$ git checkout jack (ensure sure you're changing a file in branch jack and not elsewhere) [edit Xyz.java] $ git add Xyz.java $ git commit Xyz.java $ git push master

This is all it takes. However, the git push command may complain if its copy of Xyz.java is later than the one you started with. This is where the need to resolve changes comes in.

Imagine you have a branch, jack. You've created it from master and make one change to a file in it. Meanwhile, a colleague has made a change to the same file in his branch, then pushed that change to master. Your attempt to push to master fails. What do you do? You can merge the file if the changes don't conflict (i.e.: affect the same lines in the file).

$ git checkout master $ git merge jack

This seems a little odd because most, especially Subversion, users think of merging as a pushing operation. In Git, you checkout (switch to) a branch, then merge (pull) the changes from another into it. Here's the template:

git merge [from] branch [into the current branch]

So, if the changes don't conflict, you're done. But, if there are conflicts, markers will be left in the conflicting file that you can see by using git diff:

$ git diff

You use this information to resolve the file, but editing it in the branch in which you wish to merge. Then you can commit it. (Here you see a short cut that does git add followed by git commit.)

$ git commit -a Xyz.java

Resolving differences between branches

Git knows a file has conflicts if it contains one or more occurrences of something like:

<<<<<<< HEAD text ======= different text >>>>>>> jack

The task of resolving conflicts consist in removing <<<<<<< HEAD, ======= and >>>>>>> jack, plus the text that should not survive into the committed state (i.e.: the changes), then telling Git you've resolved the conflict. There are various GUI, TUI and text tools to accomplish conflict resolution (and none of them involve psychologists).

To see if you've successfully rid yourself of conflict, use git status to ensure that the files you resolved have moved from the "Unmerged paths" section (files are listed in red) up above to the "Changes to be committed" section (files listed in green). You may need to git add edits you perform if they don't seem to show up in green (and you've gotten rid of the conflict markers).

After moving all the unmerged files up to being ready to commit, all you need to do to finish merging is to perform the commit:

$ git commit

Not all conflicts are simply as describe above. Sometimes, the conflict is the addition or removal of a file. You'll see something like:

$ git status # On branch master . . . # Unmerged paths: # (use "git add/rm ..." as appropriate to mark resolution) # # both modified: extras/loaddb.sql # both modified: extras/schema/mysql/country-data.sql # both modified: extras/schema/mysql/test-data.sql # deleted by us: src/com/acme/webplatform/service/UserService.java # added by them: src/com/acme/webplatform/util/UTCString.java #

The solution for such files could be as simple as moving ahead with their changes (if you don't care), yours, or removing the file altogether:

$ git rm extras/loaddb.sql $ git rm extras/schema/mysql/country-data.sql $ git rm extras/schema/mysql/test-data.sql $ git rm src/com/acme/webplatform/service/UserService.java $ git add src/com/acme/webplatform/util/UTCString.java

Once you've finished resolving conflicts, git status returns with nothing suggested (all green) and you're ready to commit.

$ git status # On branch master # Changes to be committed: # # deleted: extras/loaddb.sql # deleted: extras/schema/mysql/country-data.sql # deleted: extras/schema/mysql/test-data.sql # deleted: src/com/acme/webplatform/service/UserService.java # new file: src/com/acme/webplatform/util/UTCString.java . . .

Last, if you are still unable to commit, Git will say so and list the files that haven't been resolved. Perhaps you missed something. You may need to loop back through these steps again.

Resolving differences between local and remote of the same branch

When a merge to master is done, it's natural to push the changes back to master remote, which fails since local and remote master are out of sync if someone else already merged to master and pushed to remote. You see this:

$ git checkout master $ git merge jack $ git push To [email protected]:acme-server ! [rejected] master -> master (non-fast-forward) error: failed to push some refs to '[email protected]:acme-server' To prevent you from losing history, non-fast-forward updates were rejected Merge the remote changes before pushing again. See the 'Note about fast-forwards' section of 'git push --help' for details.

The solution is to pull from remote master:

$ git pull remote: Counting objects: 5, done. remote: Compressing objects: 100% (3/3), done. remote: Total 3 (delta 2), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. From tuonela.site:acme-server 6fd460c..06d6756 master -> origin/master Auto-merging build.xml CONFLICT (content): Merge conflict in build.xml Automatic merge failed; fix conflicts and then commit the result.

...then resolve any conflicts (i.e.: eliminate any <<<<<<< HEAD, ======= and >>>>>>> stuff):

$ gvim build.xml $ git add build.xml $ git commit [master d7b6535] Merge branch 'master' of tuonela.site:acme-server into master

...and, finally, push those changes back to remote:

$ git push Counting objects: 1, done. Writing objects: 100% (1/1), 274 bytes, done. Total 1 (delta 0), reused 0 (delta 0) To [email protected]:acme-server 06d6756..d7b6535 master -> master

Error during checkout

Sometimes there are files that are out of sync between repositories and your git checkout <branch> will not work because Git can't auto-merge something. To boot, you probably aren't ready even to merge or commit these changes anyway. There are messy ways around this. One is to use a GUI tool you find. Another is to stash your out-of-sync files, do the checkout and merge, then unstash:

$ git stash save "This stuff won't merge/dog won't hunt/etc." $ git checkout master $ git merge jack $ git stash apply $ git stash drop

What does this accomplish? Save off edits that you don't wish to commit, pull and merge latest edits back from master, reapply what you're halfway through working on and, finally, drop the stash since you don't need it anymore.

If you fail to drop stashes, they will accumulate. If you've used 8 of them, then you'll have to invoke git stash drop 8 times to throw them all away.

Also, git stash has 4 very useful levels:

  1. git stsh     # stash only unstaged changes to tracked files
  2. git stash    # stash any changes to tracked files
  3. git staash   # stash untracked and tracked files
  4. git staaash # stash ignored, untracked, and tracked files

Concrete example

git stash in same branch. Here's how that goes:

russ@gondolin ~/sandboxes/nifi-pipeline.master.dev/code/nifi-pipeline $ git pull remote: Enumerating objects: 79, done. remote: Counting objects: 100% (79/79), done. remote: Compressing objects: 100% (63/63), done. remote: Total 79 (delta 21), reused 0 (delta 0), pack-reused 0 Unpacking objects: 100% (79/79), done. From 10.10.10.150:code/nifi-pipeline ce23d06..e104541 master -> origin/master * [new branch] JdbcToXmlDemonstrationTests -> origin/JdbcToXmlDemonstrationTests Updating ce23d06..e104541 error: Your local changes to the following files would be overwritten by merge: jdbc/src/main/java/com/windofkeltia/processor/AbstractJdbcTo.java Please, commit your changes or stash them before you can merge. Aborting russ@gondolin ~/sandboxes/nifi-pipeline.master.dev/code/nifi-pipeline $ git stash save "Derby work, still in progress. All unit tests pass or are ignored." Saved working directory and index state On master: Derby work, still in progress. All unit tests pass or are ignored. HEAD is now at ce23d06 Creation of the Derby version of a NiFi DBCP controller in direct imitation of Devin's original (very) static JDBC mocking. russ@gondolin ~/sandboxes/nifi-pipeline.master.dev/code/nifi-pipeline $ git pull Updating ce23d06..e104541 Fast-forward jdbc/src/main/java/com/windofkeltia/processor/AbstractJdbcTo.java | 2 + jdbc/src/test/java/com/windofkeltia/processor/AbstractJdbcToTest.java | 457 ++++++++++++++++++++++++++++++++++++++++++++++++...+++++++++++++++ jdbc/src/test/java/com/windofkeltia/processor/mock/MockDummyDoubleDataSource.java | 34 +++++++++++ 3 files changed, 493 insertions(+) create mode 100644 jdbc/src/test/java/com/windofkeltia/processor/AbstractJdbcToTest.java create mode 100644 jdbc/src/test/java/com/windofkeltia/processor/mock/MockDummyDoubleDataSource.java russ@gondolin ~/sandboxes/nifi-pipeline.master.dev/code/nifi-pipeline $ git branches git: 'branches' is not a git command. See 'git --help'. russ@gondolin ~/sandboxes/nifi-pipeline.master.dev/code/nifi-pipeline $ git branch * master russ@gondolin ~/sandboxes/nifi-pipeline.master.dev/code/nifi-pipeline $ git stash apply Auto-merging jdbc/src/main/java/com/windofkeltia/processor/AbstractJdbcTo.java On branch master Your branch is up-to-date with 'origin/master'. Changes not staged for commit: (use "git add ..." to update what will be committed) (use "git checkout -- ..." to discard changes in working directory) modified: jdbc/jdbc.iml modified: jdbc/pom.xml modified: jdbc/src/main/java/com/windofkeltia/processor/AbstractJdbcTo.java modified: jdbc/src/test/java/com/windofkeltia/database/MirthFodder.java modified: jdbc/src/test/java/com/windofkeltia/processor/JdbcToXmlTest.java no changes added to commit (use "git add" and/or "git commit -a") russ@gondolin ~/sandboxes/nifi-pipeline.master.dev/code/nifi-pipeline $ mvn clean package [INFO] Scanning for projects... [INFO] ------------------------------------------------------------------------ [INFO] Reactor Build Order: [INFO] [INFO] nifi-pipeline-parent [INFO] shared [INFO] controllers [INFO] debugging [INFO] filter [INFO] hl7v2 [INFO] hl7v3 [INFO] hl7v4 . . . [INFO] Building jar: /home/russ/sandboxes/nifi-pipeline.master.dev/code/nifi-pipeline/nar/target/nifi-pipeline-4.0.0.nar [INFO] ------------------------------------------------------------------------ [INFO] Reactor Summary: [INFO] [INFO] nifi-pipeline-parent ............................... SUCCESS [ 0.499 s] [INFO] shared ............................................. SUCCESS [ 3.832 s] [INFO] controllers ........................................ SUCCESS [ 0.300 s] [INFO] debugging .......................................... SUCCESS [ 0.623 s] [INFO] filter ............................................. SUCCESS [ 0.630 s] [INFO] hl7v2 .............................................. SUCCESS [ 1.226 s] [INFO] hl7v3 .............................................. SUCCESS [ 2.093 s] [INFO] hl7v4 .............................................. SUCCESS [ 1.937 s] [INFO] jdbc ............................................... SUCCESS [ 2.251 s] [INFO] misc ............................................... SUCCESS [ 0.619 s] [INFO] mpi ................................................ SUCCESS [ 0.064 s] [INFO] integration-tests .................................. SUCCESS [ 0.049 s] [INFO] nifi-pipeline ...................................... SUCCESS [ 2.254 s] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 17.334 s [INFO] Finished at: 2020-07-16T09:36:33-06:00 [INFO] Final Memory: 58M/207M [INFO] ------------------------------------------------------------------------ russ@gondolin ~/sandboxes/nifi-pipeline.master.dev/code/nifi-pipeline $ git stash drop Dropped refs/stash@{0} (a2092925ea2928227bfd6f808853465205e2a49f)