What’s changed (Understanding diff)

(Q) How can I find the files that I’ve changed in this branch?

(A) I’ve setup an alias called git changed

git config --global alias.changed '!git diff --name-only $(git merge-base HEAD $(git symbolic-ref refs/remotes/origin/HEAD | sed "s@^refs/remotes/origin/@@"))'

This command does:

  1. Get’s the name of the branch your branch is off of (git symbolic-refs)
  2. Get’s the commit used when you created your branch (git merge-base)
  3. Runs the git command to list just the files that have changed (git diff –name-only $COMMIT_WHERE_BRANCH_WAS_CREATED)

(Q) How can I view all of the actual source code changes made to a file?

(A) Using git log -p shows every commit that modified somefile and the actual source code changes made in that commit.

git log -p -- somefile

If you want to see the diff for ALL of the files in each commit that changed somefile then you can add the --full-diff option, like this:

git log -p --full-diff -- somefile

which will show diffs made in every commit that changed somefile. This syntax -- somefile filters/finds only commits where the file (path) specified was changed.

Git resolves somefile relative to the repository root (not necessarily your current directory). So a more realistic example might be

git log -p --full-diff -- src/main/java/com/example/play/hello.java

The src is relative to the root of the repostitory you are working on. If you specify a directory then git will filter for any commit that changed ANY file within that directory (or sub-directories).

git log -p -- src/main/resources

So this will show a the difference to any file that was changed under src/main/resources.

The -p (patch) option causes the results diff results to be shown in patch format (aka diff format).

The diffs will include ALL files changed in that commit. The diffs shown will be for the commit being shown and the previous version of that file (aka the parent commit).

Diff Filters - find only commit where file was added or deleted

Filters commits to only show those where files were added (A) or deleted (D). You can include multiple filters in one --diff-filter option as shown.

git log --diff-filter=AD --name-only -- somefile

Common filters:

  • A = Added
  • C = Copied
  • D = Deleted
  • M = Modified
  • R = Renamed

Changing the output of the diff

The git log command supports a --format option which allows you to specify the format for log records. For example,

git log --format="Commit: %h by %an on %ad"
# Sample output
Commit: abc123 by Alice on Mon Feb 19 12:00:00 2024
Commit: def456 by Bob on Sun Feb 18 15:30:00 2024

(Q) How can I get a single file from another branch?

(A) As follows

FILE=the/path/to/file.txt
git show TheBranch:$FILE > $FILE.tmp
diff $FILE.tmp $FILE 

A practical example for using this was; Another developer committed to several repos that I’m working on updated the releases of those packages. I need to update my application to use the release versions that are now committed and in the master branch.

To do this, I wrote the following script:

# FILE: mygit-get-file
#!/bin/sh
LIMIT_LINES=9999999
STARTING_DIR=$(pwd)
for arg in $*
do
    cd $STARTING_DIR
    echo "FILE: $arg"
    # Give relative filename from root of REPO/Some/Path/to/a/file.txt
    # reponame=REPO
    # dirname=Some/Path/to/a/file.txt
    reponame=$(echo $arg | sed 's,/.*,,')
    dirname=${arg#*/*}
    cd $reponame
    # Use main as the master branch if it exists, otherwise default to master for backwards historical reasons
    if [ "`git branch -r | grep main`" != "" ]
    then
       MAIN_BRANCH_NAME=main
    else
       MAIN_BRANCH_NAME=master
    fi
    echo "cd $reponame; git fetch; git show origin/$MAIN_BRANCH_NAME:$dirname | head -$LIMIT_LINES)"
    git fetch; git show origin/$MAIN_BRANCH_NAME:$dirname | head -$LIMIT_LINES
    echo "------------------------------------------------------------"
done

How can I see the file somefile

git show next~10:Documentation/README

Shows the contents of the file Documentation/README as they where in the 10th to the last commit on the branch next. See https://git-scm.com/docs/git-show.

Deleting branch, tag, commit

(Q) How can I delete a tag both locally and remotely?

(A) As follows

git tag -d 1.6.3
git push --delete origin 1.6.3

(Q) How can I delete a branch both locally and remotely?

(A) A similar syntax is used to delete a local/remote branch, but branches might have issues deleting.

# git tag tbd/branch1                 # Optionally tag a branch if you might need to get back to it
git branch -d feature/branch1
git push --delete origin feature/branch1

Git will only delete the branch if it has already been fully merged into the current branch (or another specified branch).

NOTE: If the -d option doesn’t work because the branch is currently being merged or for some other reason you can use the -D option to force the deletion of the branch.

NOTE2: To get back to a branch that you deleted, you can use

git checkout -b feature/branch1 tbd/branch1

Branch Hygiene

Periodically, you’ll want or need to cleanup/delete your local branches (Q) How can I get the pom version out of a pom.xml file to use it when creating a git tag for a release? (A) MVN_VER=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)

Git tags

Git has two kinds of tags. Annotated an unannotated. To see which is which run the command

git for-each-ref refs/tags --format="%(refname:short) -> %(objecttype) (%(*objectype)) %(objectname:short) %(authordate:iso8601-strict) %(*authorname)"
25.1 -> tag (commit)
tags/jira-123-wip -> commit

# Things you can do with tags
git show jira-123-wip
git co jira-123-wip


# Then you can
git cat-file -t 25.1

So what’s the difference?

  1. An annotated tag is a GIT object, so questions about a tag, you can ask about the GIT OBJECT or the object that the tag points to.
    • In the --format argument when you see a star (authorname), the star means what the object points to so this only happens for *annotated objects.
  2. A unannotated tag is simply an alias for a hash aka a commit. Also called a reference.

What is quite confusing about git tags is how some git command work off of git objects and others work off of the pointers. An anaology (that is not perfect) is a symbolic link in UNIX. When you issue the ls (list files) commands for a directory that contains the symbolic link do you want to see the metadata about the symbolic link or the metadata (e.g., file creation date) of the file the symbolic link points to. Well, in most cases you want to operate on the file the symbolic link points to and that is how many git commands work. So in most cases you can treat a tag as a hash because that’s how it’s most often used.

To create an annotated hash you can do

git tag -a v1.0.0 -m "Release version 1.0.0"

RefLogs

You can think of reflogs as a log of every transaction that GIT does. Conceptually, it can be thought of as Git’s “undo history”.

Reflogs are local only and expire (usually after 90 days). You can configure this:

git config --global gc.reflogExpire "30 days"

<
Previous Post
How do I get git push to do what I want?
>
Next Post
React Server Components Say No