GIT - The 'r*' Git commands

mail

git rev-list

Usage

Lists commit objects in reverse chronological order, which can be used to list + count commits :

git rev-list commit1 ^commit2

  • lists commits that are reachable by commit1. This means this command starts at commit1 on the commit tree and goes back in time following the parent link from one commit to its ancestor
  • lists commits that are reachable by commit2 the same way and excludes them from the result.
Consider this very basic Git tree of commits :
	A---B---C---D---E

git rev-list D ^B
  1. lists commits reachable from D : D, C, B, A
  2. lists commits reachable from B : B, A
  3. because of the ^ before B, B, A are excluded
  4. which gives : D, C
Both commands may be used interchangeably :

Flags

Flag Usage
--count Print a number stating how many commits would have been listed, and suppress all other output.
--graph Draw a text-based graphical representation of the commit history on the left hand side of the output.

Example

Count the number of commits :

Basic shell-oriented method:
git log --oneline | wc -l
Git method:
git rev-list --count HEAD

Count the number of commits on a branch :

branchName='myBranch'; git rev-list "$branchName" ^$(git merge-base master "$branchName") --count
This assumes myBranch is based on master.

Details :

Situation : a few days ago, I created a feature branch from master and I've made many commits on that branch since that day. Now, I'd like to know how many commits I've made on that branch.
  • git rev-list --count feature
    This is not the right answer because it counts :
    • not only the commits that are on feature
    • but also all the commits made on master before the branch creation. These commits must be excluded.
  • We can use either the ^ or the .. operators to exclude commits. We just need to find the base of the feature branch on master. This can be done with git merge-base :
  • git merge-base master feature
    7ff76c29629fd9d3cf59e638b242d2a825f8e2c2
  • Now let's count commits made on feature :
    • git rev-list feature ^7ff76c2 --count
    • git rev-list feature ^$(git merge-base master feature) --count
    97
  • Let's check this with a different method :
    git log --oneline | grep -n 7ff76c2
    98:a very interesting commit message
mail

git restore

See git switch for answers to what is it ? and what is it for ?.
mail

git reflog

Usage

Manage reflog information (using this command is the sign that things are breaking bad, mostly because of some over-optimistic launch of commands )

git reflog subcommand options

Flags

Flag Usage
show gitReference show the log of gitReference
git reflog (with no further argument) produces an output that's pretty similar to git log --oneline + some extra bits. Those extras are commits that existed once but don't appear anymore in the history (once rewritten) : "amend" commits, squashed / rebased commits, ...
mail

git rebase

Usage

Reapply commits on top of another base tip, i.e. change this :
    A---B---C---D	master
         \
          E---F---G	feature
into this :
    A---B---C---D	master
                 \
                  E'--F'--G'	feature
with one of these :

As a summary :

when sitting on branchToRebase (i.e. it's the current branch) :
git rebase newBaseBranch
otherwise :
git rebase newBaseBranch branchToRebase

Managing conflicts

Rebasing (and merging) can lead to conflicts in one or more tracked files. This is usually no big deal : basically, it means 2 commits changed the same piece of code in distinct parts of the history (2 branches, or 2 commits being re-ordered) and Git doesn't know which is the "good" version.

There is no --dry-run (or equivalent) flag to simulate what will happen beforehand, but in case things break bad, git rebase --abort can cancel it all.

Rebase. Or do not. There is no try.

Should there be conflicts, you'll be warned with a message like :

Auto-merging someFile
CONFLICT (content): Merge conflict in someFile
error: could not apply 4a7406f3... commitMessage
Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".
You can instead skip this commit: run "git rebase --skip".
To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply 4a7406f3... commitMessage
At that time, you have 3 possibilities :
  1. fix the conflict in someFile + git add someFile + git rebase --continue. Congratulations : job done !
  2. for some reason, you wish you never typed the git rebase command leading to the conflict. git rebase --abort is your "back to the past" ticket . You're out of trouble so far (no data lost), but you'll have to sort things out someday.
  3. This 3rd option — git rebase --skip — is a little bit tough : it will actually rebase as if the commit causing the conflict didn't exist. Consequently, this skipped commit won't be referenced anymore and seem lost. It's not lost anyway and can be recovered using git reflog.

Flags

Flag Usage
--abort Abort the rebase operation and reset HEAD to the original branch.
If branch was provided when the rebase operation was started, then HEAD will be reset to branch. Otherwise HEAD will be reset to where it was when the rebase operation was started.
--continue Restart the rebasing process after having resolved a merge conflict
-i
--interactive
Make a list of the commits which are about to be rebased. Let the user edit that list before rebasing.
usage :
git rebase -i commitId
where commitId is the last commit that will be left as-is. In other words, commitId
  • is the commit preceding the commits that will be rebased
  • is usually the father of the "faulty" commit
--onto newBase
  • create new commits starting from newBase
  • newBase is not limited to branch names and can be any valid commit
--skip bypass the commit that caused the conflict
This commit may no longer be listed by git log but can be recovered with git reflog.
mail

git revert

Usage

Undo a single commit, and commit this :

git revert should be used to undo all the changes made by a commit.
For example, this can be useful when you're tracking down a bug and find that it was introduced by a single commit. Instead of manually going in + fixing it + creating a new FIX: commit, you can use git revert to automatically do all of this for you.

If you have no interest in a revert commit, an alternative is to fix previous commits.

git revert :

How to revert several commits ?

  • You must not forget that every commit is stacked on top of its ancestor (i.e. "it makes changes to its ancestor"). So to "undo" these changes, there's no other solution than unstacking changes in the opposite order.
  • You may issue successive revert commands or pass a list of commits to a single revert.
  • The key point is that commits must be listed in the opposite order of their creation.
  • Consider -n.

Flags

Flag Usage
--abort cancel the operation and return to the pre-sequence state
-n
--no-commit
git revert creates a new revert commit with a message stating which commit is reverted. -n applies the same changes but does not make the commit.
This is useful when reverting more than one commits' effect to your index in a row.

Example

Revert changes made by commitId :

git revert commitId
Will open an editor to enter the commit message that defaults to :
Revert "commitMessage of commitId"

	This reverts commit commitId.

Reminder : how to view changes made by commitId (source) ?

git diff commitBeforeCommitId..commitId
outputs only the diff
git show commitId
outputs the commit(s) id(s) + author(s) + commit message(s) and the diff itself
mail

git reset

Usage

Move the current branch (branch = pointer) to another position, and optionally update the index and the working directory (details, graph).

git reset can either :
  • When you undo with git reset (and the commits are no longer referenced by any ref or the reflog), there is no way to retrieve the original copy : it is a permanent undo. Care must be taken when using this tool, as it's one of the only Git commands that has the potential to lose your work
  • git reset should only be used to undo local changes; NEVER reset snapshots that have been shared with other developers

Flags

Flag Usage
--hard commitId Resets the index and working tree. Any changes to tracked files in the working tree since commitId are discarded.
If commitId is omitted, defaults to HEAD.
-p --patch Interactively select hunks to unstage a patch. This does the opposite of git add --patch
-q --quiet quiet mode : only report errors

Example

git reset idOfCommitPreceedingFaultyCommit
Move the current branch tip backward to idOfCommitPreceedingFaultyCommit, reset the staging area to match, but leave the working directory alone. All changes made since idOfCommitPreceedingFaultyCommit will reside in the working directory, which lets you re-commit the project history using cleaner, more atomic snapshots.
mail

git remote

Usage

Manage set of tracked repositories :

Flags

Flag Usage
-v verbose mode

Example

Get list and URL of remotes :

git remote -v
origin	ssh://git@git.example.com:path/to/repository/name.git	(fetch)
origin	ssh://git@git.example.com:path/to/repository/name.git	(push)

Add a new remote (source) :

  • git remote add remote git@gitlab.example.com:path/to/repository/name.git
    origin is a very common remote name. Be careful to give explicit names to your remotes or you'll get lost sooner or later (consider renaming some remotes)
  • git remote add remote ssh://bob@192.168.56.107/path/to/repository
    (details)

The new remote can even be a local directory (let's say the original repo is /path/to/original/repo/) :

  1. mkdir -p /path/to/new/repo/; cd /path/to/new/repo/
  2. git init
  3. git remote add myOriginalRepo /path/to/original/repo/
  4. git pull myOriginalRepo master -f

If the remote repository is a directory (local, or mounted or whatever), its path should be declared as /path/to/repository/.git

Delete a remote (source) :

  1. list existing remotes
  2. git remote rm remote

Update the URL of a remote repository :

This updates the URL property of the remote repository known as "origin" (updating is better than remove + add because this doesn't break the history (somewhat similar to file renames ...)) :
git remote set-url origin git@gitlab.example.com:path/to/repository/name.git

Since this will probably go to a new host, don't forget to update the SSH configuration.

Rename a remote (source) :

git remote rename oldName newName