git
Table of content
- Generic
- Config
- Commands
- Long Commands
- Git config
- Git alias
- Git exec
- Parameters for gerrit
- How to combine multiple commits into one
- Tags
- Setup gpg for signing commits or tags
- Submodules and Subtrees
- How to resolve error
- How to upload new patch set to gerrit
- How to create an empty branch
- Worktrees
- Shrink repo size
- Messages from git
- Bypassing gerrit review/limits
- How to checkout subdir from a git repo aka sparsecheckout/sparse checkout
- Change parent to newest commit
- Rebase forked repository e.g. on Github
- Find content in all commits
- Find commits or tags in located in branches
- Debug or Trace mode
- Git LFS
- Remove files from commit
- Create and apply patches
- Change Author of pushed commit
- Extentions
- Dangling Commits Tags Trees and Blobs
- Rewrite history by using difs of changes
Generic
Best Practice
In this section we want to give you some insites in best practices for working with git. Of course not all of them are valide for everyone and keep in mind, that this are suggestsions and everyone needs to decide on its own if this is helpful or not.
Commit Messages
A very nice documentaiton about best practices on git commit mesage is the follogwing https://cbea.ms/git-commit/ which is worth giving it a view.
Config
Commands | Description |
---|---|
git config core.fileMode false | ignores permission changes on files/dirs |
git -c core.fileMode=false [git-command] | ignores permission changes on files/dirs for one command e.g. git -c core.fileMode=false diff |
git config --global core.fileMode false | sets the ignore permission global, NOT A GOOD IEAD ;) |
git config --global --add safe.directory '*' | since git version 2.53.3 you can use this command to “disable” the unsave repository error (save.direcotry), as it assumes every repository on the server is save |
Commands
Commands | Description |
---|---|
GIT_TRACE=1 git <command> | enables tracing of git command + alias and external application |
git add --interactive | shows menu for interacting with index and head |
git add -p | same as add --interactive but lighter |
git archive [branchname] --format=[compression format] --output=[outputfile] | archivles branch into compressed file |
git branch [branchname] | creates local branch with [branchname] |
git branch -[d/D] [branchname] | deletes local branch with [branchname] |
git branch -m [new branchname] | renames current branch to [new branchname] |
git branch -m [old branchname] [new branchname] | renames [old branchname] branch to [new branchname] |
git branch --no-merged | lists only branches which are not merged |
git branch --show-current | displays current branch name (possible since version 2.22 ) |
git bundle create [outputfile-bundlefile] [branchname] | export a branch with history to a file |
git check-ignore * | list ignored files |
git checkout -b [branchname] | checkout and create branch |
git checkout -b --orphan [branchname] | creates branches with no parents |
git checkout [branchname] [file] | pulls file from branch to active branch |
git cherry-pick [commitid] | cherry-picks commit into current branch |
git clean -n | dry run of remove (tracked files) |
git clean -f | remove (tracked) files |
git clean -[n/f] -d | (dry run) remove (tracked) files/directories |
git clean -[n/f] -X | (dry run) remove (ignored) files |
git clean -[n/f] -X -d | (dry run) remove (ignored) files/directories |
git clean -[n/f] -x | (dry run) remove (ignored and untracked) files |
git clean -[n/f] -x -d | (dry run) remove (ignored and untracked) files/directories |
git clean -X -f | clean the files from .gitignore |
git clone /file/to/git/repo.git | clone |
git clone -b [branchname] --single-branch [repodestination] | conles only single branch from repo |
git clone -b [targetbranch] [output-bundlefile] [repositoryname] | creates new repo [repositoryname] with data from [output-bundlefile] into branch [targetbranch] |
git clone [repodestination] --depth [x] | clones repo with a depth of [x] |
git clone ssh//user@host/path/to/git/repo.git | clone |
git commit --amand --no-edit | reapplies last commit without chaning commitmsg |
git commit --fixup [sha-1] | marks your commit as a fix of a previous commit |
git commit --no-verify | bypass pre-commit and commit-msg githoos |
git commit --reuse-message=<ref_link> | This allows you to reuse a commit message after you have performed e.g. a git reset like: git commit --reuse-message=ORIG_HEAD |
git describe | shows how old the last merge/tag/branch is |
git diff branch1name branch2name /path/to/file | diffs file between branches |
git diff branch1name:./file branch2name:./file | diffs file between branches |
git diff [remotename]/[remotebranch] -- [file] | diffs the file against a fetched branch |
git diff --no-index [path/file/1] [path/file/2] | diffs two files which des not need to be part of a workingdir/git-repo |
git diff-index --quiet HEAD -- | returns 1 if there are changes to commit/add, returns 0 if nothing to do |
git fetch -p | updates local db of remote branches |
git gc --prune=now --aggressive | pruine all unreachable objects from object database |
git init --bare /path/to/git/folder.git | init bare repo |
git log [branch1] ^[branch2] | shows commits which are in [branch1] but not in [branch2] |
git log --all --full-history -- "[path]/[file]" | displays log of file, specially nice for deleted files, e.g. for unknown path/extention use "**/[filename].*" |
git log --date=relative | displays date relative to your current date |
git log --diff-filter=D --summary | displays/shows all deleted files/folders |
git log --show-signature | displays the signature if it got signed |
git log --pretty=email --patch-with-stat --reverse --full-index --binary -m --first-parent -- [file(s)_to_export] [/path/to/new/patchfile] | exports file(s)/dir(s) with git history as a patch |
git log -S [string] | searches for changes on [string] in the changesset (pickaxe functionalitys) |
git log -G [string] | searches for any mention of [string] in the changessets (pickaxe functionality) |
git merge --no-ff | forces merge commit over branch |
git name-rev --nameonly [sha-1] | check if the change was part of a release |
git push [remote_name] :[branchname] | deletes remote branch with [branchname] |
git push [remote zb origin] :refs/tags/[tag_string_name] | removes tag from remote |
git push -d [remote_name] [branchname] | deletes remote branch with [branchname] |
git push --force-with-lease [remote] [branchname] | force psuh but still ensure you don’t overwirte other’s work |
git rebase -i --root | rebases the history including the very first commit |
git rebase --autostash | stashes changes before rebasing |
git stash | stashes uncommited changes |
git stash -- <file1> <file2> <fileN> | stashes uncommited changes of specific file(s) |
git stash branch [branchname] | branch off at the commit at which the stash was originally created |
git stash clear | drops all stashes |
git stash drop | remove a single stashed state from the stash list |
git stash list | lists all stashes |
git stash pop | remove and apply a single stashed state from the stash list |
git stash show | show the changes recorded in the stash as a diff |
git stash push -m "<stage message>" | creates stash with message “ |
git stash push -m "<stage message>" <file1> <file2> <fileN> | creates stash with message “ |
git stash push -m "<stage message>" --staged | creates stash with message “ |
git stash --keep-index ; git stash push -m "<stage_name>" | same as above, but long way |
git rm --cached [file] | removes file from index |
git rm --cached -r [destination e.g. file . ./\*] | removes the added stuff as well |
`git remote show [remotename] | sed -n ‘/HEAD branch/s/.*: //p’` |
git reset --hard [ID] | rest to commit |
git reset HEAD^ --hard | deletes last commit |
git reset HEAD^ -- [filename] | removes file from current commit |
git restore --source=HEAD^ -- [filename] | restores [filename] from source HEAD^ (last commit), but does not remov file from current commit |
git restore --source=HEAD^ --staged -- [filename] | restores [filename] from source HEAD^ (last commit) and stages the file, but does not remov file from current commit |
git rev-parse --abbrev-ref HEAD | returns current branch name |
git rev-parse --is-inside-work-tree | returns true or false if targeted directory is part of a git-worktree |
git rev-parse --show-toplevel | shows the absolut path of the top-level (root) directory |
git revert [ID/HEAD] | will revert the commit with the ID or the HEAD commit |
git shortlog | summarize the log of a repo |
gtt shortlog --all --summary | same as above but counts all branches in as well and returns the counter for commits per author |
git show-branch | shows branches and there commits |
git status --ignored | status of ignored files |
git tag -d [tag_string_name] | removes tag |
git tag -v [tag] | verifies gpg key for tag |
git update-index --assume-unchanged [filename] | don’t consider changes for tracked file |
git update-index --no-assume-unchanged [filename] | undo assume-unchanged |
git verify-commit [gpg-key] | verifies gpg key for commit |
git whatchanged --since=[time frame] | shows log over time frame e.g. ‘2 weeks ago’ |
Long Commands
Apply commit from another repository
$ git --git-dir=<source-dir>/.git format-patch -k -1 --stdout <SHA1> | git am -3 -k
or
git -C </path/to/other/repo> log --pretty=email --patch-with-stat --reverse --full-index --binary -m --first-parent -- <file1> <file2> | git am -3 -k --committer-date-is-author-date
Get all commits from all repos
This command allows you to count all comits on all branches for all users.
Be aware, squashed commits which are not stored on another branchs seperatly and not-squashed are counted as 1 commit of course
This has to be executred on the bare repos, to ensuare that all needed information is available on the fs
This sample was created with gitea as code hosting application where repos can be stored beneath organisations and personal accounts and limits to the year 2002
$ for f in $(ls -1 /gitea_repos) ; do for i in $(ls -1 /gitea_repos/$f) ; do echo "$f/$i" ; git -C /gitea_repos/$f/$i shortlog --all --summary --since "JAN 1 2002" --until "DEC 31 2002" | awk '{print $1}' ; done ; done | awk '{s+=$1} END {print s}'
If you want to have it for the full live time of the repo, just remove the options
--since
and--until
with there values.
Git config
The .gitconfig
allows you to include more files into the configuration by using the include section:
[user]
name = mr robot
email = mr.robot@localhorst.at
[include]
path = /path/to/the/file/you/want/to/include
All the configuration you are doing in the file which is getting included will be instantly applied to your config when you save it.
This makes the shearing of similar parts of you config very easy.
Git alias
git
is able to use aliases meaning you can shorten longer commands like in bash into a short word.
These alias are defined in your normal .gitconfig.
A very useful alias, is the alias alias ;)
alias = !git config --get-regexp '^alias.' | colrm 1 6 | sed 's/[ ]/ = /'
It will print you all your configured aliases form the .gitconfig and there included files.
When you are using aliases, you can run git internal commands (e.g. push, cherry-pick,..) but you can also run external commands like grep
,touch
,…
ps = push
cm = commit
cma = cm --amend
fancy = !/run/my/fancy/script
To execute now such aliases, just type git
$ git cm -m "testing"
$ git cma
$ git ps
$ git fancy
You have to think about two thing while using external commands in the aliases:
- Don’t separate commands with a semicolon (;) combine them with two ands (&&)
- if you need the current path where you are located, you have to add a command before your original command in the alias
Note that shell commands will be executed from the top-level directory of a repository, which may not necessarily be the current directory.
GIT_PREFIX
is set as returned by running git rev-parse –show-prefix from the original current directory
In the .gitconfig you would need add cd -- ${GIT_PREFIX:-.} &&
to your alias
[alias]
fancy = !cd -- ${GIT_PREFIX:-.} && /run/my/fancy/script
Extended alias
Extended alias are known as git
alias which are calling e.g. shell functions.
These can be very helpful if you have to execute complex commands and/or you want to deal with (multible) parameters inside your alias.
Lets see how a simpe extended alias could look like:
[alias]
addandcommit = "! functionname() { for file in ${@} ; do git add \"${file}\" ; echo "File ${file} added" ; done ; sleep 5 ; git commit ; }; functionname"
By adding the above alias, you could do for example this:
$ git addandcommit ./file1 ./file2 ./file3 ./file4
File file1 added
File file2 added
File file3 added
File file4 added
#sleeps now ;) and opends your editor to place your commit message:
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# On branch master
# Your branch is up to date with 'origin/master'.
#
# Changes to be committed:
# modified: file1
# modified: file2
# modified: file3
# modified: file4
#
Git exec
Sometimes an alias is to less what you would need from git to do. For this cases you can create scripts which are executed by git.
Of course you could create an alias, something like this:
[alias] run_script = !/home/user/my_git_script.sh
But there is a different way as well, you could make use fo the git
exec path.
To get the information where this is on your system, just run git --exec-path
and you will get something like this:
$ git --exec-path
/usr/lib/git-core
Now we know in our example, that the path is /usr/lib/git-core
. In there you can place all sorts of scripts, just ensure the following:
- prefix of script file:
git-
- file is executeable
- file has a shebang
In there you can place now every sort of script to exend your git
workflow.
One more thing worth to mention.
git
not only looks into the exec path, it also looksup all direcotries you have specified in your$PATH
variable.Same requirements as for the scritps in the exec path (prefix,permissions,shebang)
Parameters for gerrit
$ git push ssh://user@gerritserver:port/path/to/repo HEAD:refs/for/master%private # pushes the commited change to gerrit and sets it in private mode
$ git push ssh://user@gerritserver:port/path/to/repo HEAD:refs/for/master%remove-private # removes the private flag from the commit in gerrit
$ git push ssh://user@gerritserver:port/path/to/repo HEAD:refs/for/master%wip # pushes the commited change to gerrit and sets it to work inprogress
$ git push ssh://user@gerritserver:port/path/to/repo HEAD:refs/for/master%ready # removes the wip flag fromt the commit in gerrit
How to combine multiple commits into one
$ git fetch <remote>/<branch> # fetching data from a different remote and brach e.g. upstream/master
$ git merge <renite>/<branch> # merge upstream/master into origin/<current branch> e.g. origin/master
$ git rebase -i <AFTER-THIS-COMMIT> # interactive rebase to bring commits together in one
# there you now have to change all the "pick"ed stated commits to "squash" or "fixup" beginnig from the second commit from the top
# now all those commits will be combined into one
# maybe you have to resolve some issues, just modify the files and add it after wards and continue with the rebase
$ git rebase --continue # continues with rebase after resoling issues
$ git push
Tags
git
is able to craete tags which is a pointer to a commit, similar to branch, without the posibility of performing changes.
There are two and a half different kinds of tags
- lightweight tags
- annotated tags
- signed tags (are in general annotated tags which got signed, more details about singed tags, can be found here: Setup gpg for signing commits or tags)
An annotated tag allows you to add a message to the tag it self, which will be shown then in
git show
command.
To tag a commit in the past, yout add the commit id at the very end of the tag command (works for all kinds of tags)
Lighweight Tags
To craete a lightweight tag, you just need to run:
$ git tag <tagname>
like:
$ git tag v0.0.6
A lighweight tag will be created as so, as long as you don’t add
-a
,-s
,-S
or-m
to thegit-tag
command.
Annotated Tags
Creating annotated tags is nearly the same as creating lightweight tags
$ git tag -a <tagname> -m "tag message"
$ git tag <tagname> -m "tag message"
$ git tag -a v0.0.7 -m "Mr. Bond"
$ git tag -a v0.0.7 "Mr. Bond"
If you do not add
-m <tag message>
(only if-a
is given),git
will open you default editor like for commit mesages, so that you can enter them in there.
By using git-show <tagname>
you will be able to see the tag message.
$ git show 0.0.7
tag 0.0.7
Tagger: Name of the tager <mail.of.the@tag.er>
Date: Fri Dof 13 13:37:42 2002
annotated-test
commit 1234345456123434561234345612343456sfgasd (tag: 0.0.7)
Author: Name of commit author <mail.of.commit@au.thor>
Date: Fri Jul 13 00:00:01 2002
My fancy shmancy commit message for our new double 0 agent
New_00_Agent.txt
List Tags
Listing tags can be done in several ways.
To get a list of all tags, just run git tag
:
$ git tag
1.0
1.1
1.2
2.0
2.0.1
2.1
2.2
To get a list of a range you would use `git tag -l “
$ git tag -l "1.*"
1.0
1.1
1.2
$ git tag -l "2.0*"
2.0
2.0.1
To get the latest tag in the git repository, use git describe --tags
:
$ git describe --tags
0.0.1-10-g434f285
Switch to Tag
In newer git
version you are able to use git-switch
to create branches or swtich to branches instead of using git-checkout
, but for tags, git-checkout
is still used.
$ git checkout <tagname>
$ git checkout 0.4.0
Note: switching to '0.4.0'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:
git switch -c <new-branch-name>
Or undo this operation with:
git switch -
Turn off this advice by setting config variable advice.detachedHead to false
HEAD is now at 5cc5bb0 git_branch_commit_review.sh test
And after that you will have a detached HEAD with the tag as its base.
Delete Tags
To delete a tag, you will have to add the parameter -d
for local deletion.
$ git tag -d <tagname>
But this will only delete local
To delete on the remote you have to perform another command.
$ git push origin :<tagname>
It could be that you have a naming conflict and maybe want to specify it more clearly, then you would add the absolut path of the tag.
$ git push origin :/refs/tags/<tagname>
Setup gpg for signing commits or tags
GPG
can be used to sign commits or tags.
To do that you have to configure it with two small commands.
$ git config --global user.signingkey <your-gpg-key-id>
$ git config --global commit.gpgsign true
If this is set you can git will automatically sign your commits or tags
If you dont want to let it do automatically, just dont execute the second line.
For performing manuall signings, you have to use than -s
or -S
as shown below:
Signed Tags
$ git tag -s v1.5 -m 'my signed 1.5 tag'
You need a passphrase to unlock the secret key for
user: "This MyName (Git signing key) <mymail@address.com>"
4968-bit RSA key, ID <gpg-key-id>, created 2014-06-04
Signed Commits
$ git commit -a -S -m 'Signed commit'
You need a passphrase to unlock the secret key for
user: "This MyName (Git signing key) <mymail@address.com>"
4968-bit RSA key, ID <gpg-key-id>, created 2014-06-04
[master 5c3386c] Signed commit
4 files changed, 4 insertions(+), 24 deletions(-)
rewrite Rakefile (100%)
create mode 100644 lib/git.erb
#### also on merge possible
$ git merge --verify-signatures -S signed-branch
Commit 13ad65e has a good GPG signature by This MyName (Git signing key) <mymail@address.com>
You need a passphrase to unlock the secret key for
user: "This MyName (Git signing key) <mymail@address.com>"
4968-bit RSA key, ID <gpg-key-id>, created 2014-06-04
Merge made by the 'recursive' strategy.
README | 2 ++
1 file changed, 2 insertions(+)
Verify Signed Commits and Tags
To verify a commit, you can use for example:
$ git show <commitid> --show-signature
And to verify a tag, you can use:
$ git tag <tagname> -v
Submodules and Subtrees
The simplest way to think of subtrees and submodules is that a subtree is a copy of a repository that is pulled into a parent repository while a submodule is a pointer to a specific commit in another repository.
This difference means that it is trivial to push updates back to a submodule, because we’re just pushing commits back to the original repository that is pointed to, but more complex to push updates back to a subtree, because the parent repository has no knowledge of the origin of the contents of the subtree.
Submodules
Generate submodule
Thirst of all you need to be in a git workign dir and the submodule-repo which you want to use need to exist already
than you are just going to execute something like that:
git submodule add <user>@<server>:<port></path/to/repo> <local/path/of/submodule/where/it/should/be/included>
e.g. git submodule add suchademon@sparda:/git/configs/newhost newhost
This will clone you the submodule into the destination path with the master branch
Next step is to initialise the submodule
git submodule init
Now you can do an update using submodule
git submodule update
And of course you can use it with all the normal functions of git if you just cd into it
Removing a submodule works like that:
git submodule deinit <local/path/of/submodule/where/it/should/be/included> -f
Than you will have to drop the .gitmodule files as well and you are done
Download submodule
Go into the git directory and update the .gitmodules
git submodule sync
With status you can see the current state
git submodule status
12341234123412341234adfasdfasdf123412347 submodule1 (remotes/origin/HEAD)
-45674567456745674567dghfdfgh54367dfggh4f submodule2
As you see above, one submodule is already available in your working directory (submodule1)
The second is not loaded right now to your working directory.
Now update with init parameter your modules to update all
git submodule update --init
Or specify the module name:
git submodule update --init submodulename
Run again the status and you will see that the second one is available
git submodule status
12341234123412341234adfasdfasdf123412347 submodule1 (remotes/origin/HEAD)
-45674567456745674567dghfdfgh54367dfggh4f submodule2 (heads/master)
Update submodule
Go into the git directory and update the .gitmodules
git submodule sync
Run the update command to do what it is used for ;) like this to update all
git submodule update
git submodule update --init --recursive
git submodule update --remote
git submodule foreach git pull
Or specify the module with its name
git submodule update submodulename
Subtrees
Adding a subtree
Same as for the submodule, you need to be in a git repo and the target repo (subtree) needs to exists.
$ git subtree add --prefix <local/path/in/repo> <user>@<server>:<port></path/to/repo> <branch> --squash
This will clone the remote repository into your <local/path/in/repo>
folder and create two commits for it.
The first is the squashing down of the entier history of the remote repository that we are cloning and the second one will be a merge commit.
If you run git status
, you will see nothing, as git subtree
will has created the commits for you and left the working copy clean.
Also there will be nothing in the <local/path/in/repo>
to indicate that the folder ever came from another git repository and as with submodules, this is both an advantage and disadvantage.
Update subtree
To update a subtree, you just need to perform:
$ git subtree pull --prefix <local/path/in/repo> <user>@<server>:<port></path/to/repo> <branch> --squash
and it will get updated.
Push to a subtree
Things get really tricky when we need to push commits back to the original repository. This is understandable because our repository has no knowledge of the original repository and has to figure out how to prepare the changes so that they can be applied to the remote before it can push.
$ git subtree push --prefix <local/path/in/repo> <user>@<server>:<port></path/to/repo> <branch>
As sad, it needs to first know how to prepare the changes which can take a while.
How to resolve error
object file is empty
cd <git-repo>
cp -a .git .git-bk # backup .git folder
git fsck --full # lists you the empty/broken files
error: object file .git/objects/8b/61d0135d3195966b443f6c73fb68466264c68e is empty
fatal: loose object 8b61d0135d3195966b443f6c73fb68466264c68e [..] is corrupt
rm <empty-file> # remove the empty file found by fsck --full
#<-- continue with the stepes git fsck --full and rm till you get the message -->
Checking object directories: 100% (256/256), done.
Checking objects: 100% (6805/6805), done.
error: HEAD: invalid sha1 pointer <commit-id>
error: refs/heads/master: invalid sha1 pointer <commit-id>
git reflog # check for the HEAD if its is still broken
tail -4 .git/logs/refs/heads/master # get the last 4 lines from the master log
ea543250c07046ca51676dab4e65449a06387cda 6a6edcc19a258834e0a68914c34823666f61979c root@np-nb-0024 <oliver.schraml@payon.com> 1470993378 +0200 commit: committing changes in /etc after apt run
6a6edcc19a258834e0a68914c34823666f61979c 473a418f519790843fcaeb2e0f6b5c406e11c1db root@np-nb-0024 <oliver.schraml@payon.com> 1470993386 +0200 commit: committing changes in /etc after apt run
473a418f519790843fcaeb2e0f6b5c406e11c1db acafb909ef399f5eb4105e03bd0ffa1817ada8ac root@np-nb-0024 <oliver.schraml@payon.com> 1471325259 +0200 commit: committing changes in /etc after apt run
git show <commit-id from the last line the first one> # check the diff of the commit
git show 473a418f519790843fcaeb2e0f6b5c406e11c1db
git update-ref HEAD <commit-id that you have checked the diff> # set the HEAD to the commit id
git update-ref HEAD 473a418f519790843fcaeb2e0f6b5c406e11c1db
git fsck --full # check again for empty/broken files
rm .git/index ; git reset # remove index from git and reset to unstage changes
git add . ; git cm <commit-message> ; git ps
gpg failed to sign the data
If you get something similat to this, it might be that you gpg
key is expired.
How could you know this, without looking directly into gpg --list-key
.
First we need to know what command fails, to get this information, we use the environment variable GIT_TRACE
like this:
$ GIT_TRACE=1 git cm "my fancy comit message"
20:25:13.969388 git.c:745 trace: exec: git-cm 'mmy fancy comit message'
20:25:13.969460 run-command.c:654 trace: run_command: git-cm 'mmy fancy comit message'
20:25:13.969839 git.c:396 trace: alias expansion: cm => commit -m
20:25:13.969856 git.c:806 trace: exec: git commit -m 'mmy fancy comit message'
20:25:13.969866 run-command.c:654 trace: run_command: git commit -m 'mmy fancy comit message'
20:25:13.972306 git.c:458 trace: built-in: git commit -m 'mmy fancy comit message'
20:25:13.979014 run-command.c:654 trace: run_command: /usr/bin/gpg2 --status-fd=2 -bsau DADCDADCDADCDADCDADCDADCDADCDADCDADC1337
error: gpg failed to sign the data
fatal: failed to write commit object
Now we know what command failed and can execute it manually:
$/usr/bin/gpg2 --status-fd=2 -bsau DADCDADCDADCDADCDADCDADCDADCDADCDADC1337
[GNUPG:] KEYEXPIRED 1655317652
[GNUPG:] KEY_CONSIDERED DADCDADCDADCDADCDADCDADCDADCDADCDADC1337 3
gpg: skipped "DADCDADCDADCDADCDADCDADCDADCDADCDADC1337": Unusable secret key
[GNUPG:] INV_SGNR 9 DADCDADCDADCDADCDADCDADCDADCDADCDADC1337
[GNUPG:] FAILURE sign 54
gpg: signing failed: Unusable secret key
And on the second line of the output, you can see that the key gets mentined as expiered.
does not point to a valid object
If you see the following error, it means that the shown reference does not point to an existing object any more.
error: refs/remotes/<REMOTENAME>/<REFERENCE> does not point to a valid object!
This could happen for example, when a commit gets revied which got a tag assigned.
To remove the broken reference you can use the command
$ git update-ref -d <refs/remotes/<REMOTENAME>/<REFERENCE>
This will delete the specified reference in your local repository.
If you have multible of thease dead references, you can list first all existing references and based on the validate get them removed. This works, as the broken references can’t be verified sucessfully and you can use the retunr code of the exeuction to perform the delete command.
$ git for-each-ref --format="%(refname)"
refs/heads/master
refs/remotes/origin/8140_5037
refs/remotes/origin/HEAD
refs/remotes/origin/master
refs/remotes/origin/borken_ref
Now use the show-ref --verify
to verify the reference and get the last commit behind it.
$ ...
$ git show-ref --verify refs/remotes/origin/master
123412341234qasdasdfasdf123412341234asdf refs/remotes/origin/master
# returns with 0
$ git show-ref --verify refs/remotes/origin/borken_ref
fatal: git show-ref: bad ref refs/remotes/origin/borken_ref (98079870987asdfasdf0987098asd7f098asdf89)
# returns with 1
You can also use
git log -n1 --online
to get a list of invalid objects and get them shown above the original commit(log)line.
And now you would use the mentioned broken ref int the delete command. To run this as a one-liner, you can do it like this.
$ git for-each-ref --format="%(refname)" | while read broken_ref ; do git show-ref --quiet --verify "${borken_ref}" 2>/dev/null || git update-ref -d "${borken_ref}" ; done
The parameter
--quiet
got added, to ensure that nothig will be printed (except of errors, thats why we redirectstderr
to/dev/null
as well).
How to upload new patch set to gerrit
# get patchset you whant to change (open the change in gerrit and go to Download > Checkout...):
git fetch ssh://<USER>@<GERRITSERVER>:<GERRITSERVERPORT>/path/of/repo refs/changes/XX/XXXX/<PATCHSETNR> && git checkout FETCH_HEAD
# now you can start to work on the fixes
cat,mv,rm,echo,.... blablabla
# after that command you will have the changes in your current branch, so create a new local branch:
git checkout -b <WORKINGBRANCH> # e.g. git co -b FIXissue
# now its time to add your change and place the commit message
git add .
# while editing your commit message, keep always the GERRIT internal Change-Id, without it wont work
git commit --amand --no-edit
# now you are going to push the changes into a new patchset:
git push origin <WORKINGBRANCH>:refs/drafts/<DESTINATION_BRANCH_WHICH_HOLDS_THE_CHANGE_TO_MODIFY>
How to create an empty branch
#creates local branch with no parents
git checkout -b --orphan testbranch
#remove cached files
git rm --caced -r .
#remove the rest
rm -rf $(ls | grep -Ev "^\.git/$|^\./$|^\.\./$")
Worktrees
Worktrees are paths on your local filesystem, where (different) branches can be checked out from the same repository at the same time. You always have at least one worktree inside your git repositorie, which contain the current path of the local repo on the currently checkedout branch.
If you are wondering why you should make use of it, it can help you deal with hughe changes while still having the main branch available on your system.
This allows you to continue with your changes in one directory, while being able to use the content of the e.g. main branch in another directory, so you dont need to stash or commit changes before you switch to the other branch again, wich can save you some time.
One very nice benefit of worktrees is, that if you update the “main” repository, your updates performed on the other worktrees, will fetch the data from the original repository, means you can save network resources, if you fully update the “main” repository with e.g. git fetch --all
as long as you have them tracked there (git branch --track [branchname]
).
List worktrees
With the command git worktree list
you can display all current worktries which are used by this repository.
The format will look like this: /path/to/repo <short_commit_id> [<branchname>]
Sample:
$ git worktree list
/home/awesomeuser/git_repos/dokus ae8f74f [master]
If you want to make read the information about worktree
s via a script, it is recommended to add the parameter --porcelain
, according to the documentation it is then easier to parse.
$ git worktree list --porcelain
worktree /home/awesomeuser/git_repos/dokus
HEAD ae8f74fae8f74fae8f74fae8f74fae8f74fae8
branch refs/heads/master
If you would have more then one
worktree
, they would be seperated with a emptyline, which looks like this:$ git worktree list --porcelain worktree /home/awesomeuser/git_repos/dokus HEAD ae8f74fae8f74fae8f74fae8f74fae8f74fae8 branch refs/heads/master worktree /home/awesomeuser/git_repos/dokus2 HEAD ae8f74fae8f74fae8f74fae8f74fae8f74fae9 branch refs/heads/master
Add a new worktree
To add a new worktree, you just simply run the following command: git worktree add [path] [branchname]
Sample:
$ git worktree add ../dokus_wt_test wt_test
Preparing worktree (checking out 'wt_test')
HEAD is now at d387057 wt_test
And you have created a new worktree
at the given path where the HEAD
is set to the given branch
:
$ git worktree list
/home/awesomeuser/git_repos/dokus ae8f74f [master]
/home/awesomeuser/git_repos/dokus_wt_test d387057 [wt_test]
In this second worktree you will find one
.git
file and nothing more.It contains the path to the original git directory:
$ cat /home/awesomeuser/git_repos/dokus_wt_test/.git gitdir: /home/awesomeuser/git_repos/dokus/.git/worktrees/wt_test ``
Failed to checkout branch on worktree
It can happen that if you create a new worktree, that git is not able to switch the branch in there. This means that if you cd
into it, the master branch will be still the active HEAD
, even it looks like this:
$ git branch -vv
a_new_branch 7ee03ca [origin/a_new_branch] First test commit
* main 0bf570d [origin/main] Second test commit msg
+ new_worktree 0bf570d (/path/to/repos/testRepo_new_worktree) Second test commit msg
And you tried to run git switch
or git checkout
which resulted into something like this:
fatal: 'new_worktree' is already checked out at '/path/to/repos/testRepo_new_worktree'
Then you can add to git checkout
the parameter --ignore-other-worktrees
and all will be good again.
Why is that so, because git only allows you to checkout a branch only for one worktree, for some reason it thinks that your current location is not the work tree so it won’t let you perform a “second” checkout and by adding the parameter --ignore-other-worktrees
this mechanism gets just (as the parameter says) ignored.
Detect if current dir is an additional worktree or the main repository
There are three easy ways to get this done.
- Is to validate if .git is a file or a dir, if it is a gitlink, then this is not the main repository, but it still could be a submodule/worktree/… (would not recommend as git submodules use it as well)
- Comparing the output of the commands
git rev-parse --absolute-git-dir
andgit rev-parse --path-format=absolute --git-common-dir
(can provide inccorect data if it is below version ~2.13)
I guess no need for a sample on checking option 1, validation if something is a file or a dir should be possible if you use already git and have found this docu ;)
if [[ $(git rev-parse --absolute-git-dir) == $(git rev-parse --path-format=absolute --git-common-dir) ]] ; then
echo "Main repository"
else
echo "Additionnal workdir"
fi
Shrink repo size
# option one
# if you added the files, committed them, and then rolled back with git reset --hard HEAD^, they’re stuck a little deeper. git fsck will not list any dangling/garbage commits or blobs, because your branch’s reflog is holding onto them. Here’s one way to ensure that only objects which are in your history proper will remain:
git reflog expire --expire=now --all
git repack -ad # Remove garbage objects from packfiles
git prune # Remove garbage loose objects
# option two
git reflog expire --expire=now --all
git gc --prune=now
git gc --aggressive --prune=now
git remote rm origin
rm -rf .git/refs/remotes
git gc --prune=now
Messages from git
git pull origin
From ssh://SERVERNAME/git/REPO
* branch HEAD -> FETCH_HEAD
* fatal: refusing to merge unrelated histories
#This (unralted history/histories) can be solved by using the parameter
--allow-unrelated-histories
Bypassing gerrit review/limits
Is useful e.g. if gerrit returns some limit issues
git push -o skip-validation ...
How to checkout subdir from a git repo aka sparsecheckout/sparse checkout
Create dir and create + add repo url
mkdir <repo> && cd <repo>
git init
git remote add -f origin <url>
# Enable sparse checkout
git config core.sparseCheckout true
# configure subdir(s)
vim .git/info/sparse-checkout
# or
echo "/dir1" >> .git/info/sparse-checkout
echo "/dir2/subdir1" >> .git/info/sparse-checkout
# now you just need to pull
git pull origin master
sparse checkout as a function
function git_sparse_clone() (
rurl="$1" localdir="$2" && shift 2
mkdir -p "$localdir"
cd "$localdir"
git init
git remote add -f origin "$rurl"
git config core.sparseCheckout true
# Loops over remaining args
for i; do
echo "$i" >> .git/info/sparse-checkout
done
git pull origin master
)
how to call the function
git_sparse_clone "http://github.com/tj/n" "./local/location" "/bin"
Change parent to newest commit
If you forgot to update the repo before you started to do your work you and have already commit your changes
you still can change the parent. This sample shows it with gerrit as review backend.
If you don’t have gerrit just skip the gerrit parts (git review…)
git pull # don't forget it now ;)
to get the change from gerrit (if you have already removed it)
git review -d <change-id>
rebase your changes to the master
git rebase master
it could the that you have merge conflicts, resolve them as usual rebase --continue
is only needed if you have merge conflicts
vim ... ; git add ; git rebase --continue
afterwards push your changes back to gerrit
git review
or push it to some other destination
git ps
Rebase forked repository (e.g. on Github)
Add the remote, call it “upstream”:
git remote add upstream https://github.com/whoever/whatever.git
Fetch all the branches of that remote into remote-tracking branches, such as upstream/master:
git fetch upstream
Make sure that you’re on your master branch:
git checkout master
Rewrite your master branch so that any commits of yours that aren’t already in upstream/master are replayed on top of that other branch:
git rebase upstream/master
If you don’t want to rewrite the history of your master branch, (for example because other people may have cloned it) then you should replace the last command with
git merge upstream/master
However, for making further pull requests that are as clean as possible, it’s probably better to rebase.
If you’ve rebased your branch onto upstream/master you may need to force the push in order to push it to your own forked repository on GitHub. You’d do that with:
git push -f origin master
You only need to use the -f the first time after you’ve rebased.
Find content in all commits
With this command you can search for content in all dirs (git repos) beneath your current position.
$ mysearchstring="test"
$ for f in *; do echo $f; git -C $f rev-list --all | xargs git -C $f grep "${mysearchstring}"; done
repo1
repo2
repo3
5b8f934c78978fcbfa27c86ac06235023e602484:manifests/dite_certs_access.pp:# echo "we are pringint test"
repo4
Find commits or tags in located in branches
git branch --contains=<tig|commitid>
allows you to search for tags/commitid in al branches and returns the branch names
$ git branch --contains=7eb22db
branchA
* master
my1branch
my3branch
Debug or Trace mode
To debug commands like push
, pull
, fetch
and so on, you can use the variables GIT_TRACE=1
and GIT_CURL_VERBOSE=1
to get more details.
There are also other debug variables which can be set, some of them we have listed at Huge debug section
Debug enabled via shell export
As alredy sad in the header, you can use export to ensure debug is enabled
$ export GIT_TRACE=1
$ git ...
Debug enabled via prefix parameter
alg
is alreay an existing alias in my case which allows me to grep for aliases ;)
$ GIT_TRACE=1 git alg alg
16:54:11.937307 git.c:742 trace: exec: git-alg alg
16:54:11.937375 run-command.c:668 trace: run_command: git-alg alg
16:54:11.940118 run-command.c:668 trace: run_command: 'git config --get-regexp '\''^alias.'\'' | colrm 1 6 | sed '\''s/[ ]/ = /'\'' | grep --color -i' alg
16:54:11.944259 git.c:455 trace: built-in: git config --get-regexp ^alias.
alg = !git config --get-regexp '^alias.' | colrm 1 6 | sed 's/[ ]/ = /' | grep --color -i
Debug enabled via alias
You can even create an git
alias which you could execute for this in your [alias]
git-config section
[alias]
debug = !GIT_TRACE=1 git
Huge debug
If you really want to see add the following vars:
GIT_TRACE=1 GIT_CURL_VERBOSE=1 GIT_TRACE_PERFORMANCE=1 GIT_TRACE_PACK_ACCESS=1 GIT_TRACE_PACKET=1 GIT_TRACE_PACKFILE=1 GIT_TRACE_SETUP=1 GIT_TRACE_SHALLOW=1 git <rest of git command> <-v if available>
Parameters and there descriptsion:
GIT_TRACE
: for general tracesGIT_TRACE_PACK_ACCESS
: for tracing of packfile accessGIT_TRACE_PACKET
: for packet-level tracing for network operationsGIT_TRACE_PERFORMANCE
: for logging the performance dataGIT_TRACE_SETUP
: for information about discovering the repository and environment it’s interacting withGIT_MERGE_VERBOSITY
: for debugging recursive merge strategy (values: 0-5)GIT_CURL_VERBOSE
: for logging all curl messages (equivalent to curl -v)GIT_TRACE_SHALLOW
: for debugging fetching/cloning of shallow repositories
Possible values can include:
true
1
2
: to write to stderr
Sample debug commands interacting with remotes
This is a smal sample who a git pull could look like in trace mode
$ GIT_TRACE=1 GIT_CURL_VERBOSE=1 git pull
12:25:24.474284 git.c:444 trace: built-in: git pull
12:25:24.476068 run-command.c:663 trace: run_command: git merge-base --fork-point refs/remotes/origin/master master
12:25:24.487092 run-command.c:663 trace: run_command: git fetch --update-head-ok
12:25:24.490195 git.c:444 trace: built-in: git fetch --update-head-ok
12:25:24.491780 run-command.c:663 trace: run_command: unset GIT_PREFIX; ssh -p 3022 gitea@gitea.sons-of-sparda.at 'git-upload-pack '\''/oliver.schraml/spellme.git'\'''
12:25:24.872436 run-command.c:663 trace: run_command: git rev-list --objects --stdin --not --all --quiet --alternate-refs
12:25:24.882222 run-command.c:663 trace: run_command: git rev-list --objects --stdin --not --all --quiet --alternate-refs
12:25:24.887868 git.c:444 trace: built-in: git rev-list --objects --stdin --not --all --quiet --alternate-refs
12:25:25.018760 run-command.c:1617 run_processes_parallel: preparing to run up to 1 tasks
12:25:25.018788 run-command.c:1649 run_processes_parallel: done
12:25:25.018801 run-command.c:663 trace: run_command: git gc --auto
12:25:25.021613 git.c:444 trace: built-in: git gc --auto
12:25:25.026459 run-command.c:663 trace: run_command: git merge --ff-only FETCH_HEAD
12:25:25.029230 git.c:444 trace: built-in: git merge --ff-only FETCH_HEAD
Already up to date.
Git LFS
The git large file support or git large file system was designed to store huge files into git
$ apt install git-lfs
As LFS was designed for http(s) interactions with the git repository it des not natively support ssh commands. This means, that you need to authenticate against your remote destination frist, befor you puch sour changes. There are two ways to do so
- either you change your remote destination to the https it will work instandly, but, than you have to enter all the time the username and pwd.
- if you want to do this via ssh, you hopefully have an gitea instance running, as that one is supporting it with a small lfs helper.
Gitea LFS helper authentication
To authenticate against lfs before you push your change, you can run the command like that sample below
$ ssh ssh://<host> git-lfs-authenticate <repo_owner>/<repo_name> download
LFS real live sample
$ ssh ssh://gitea.sons-of-sparda.at git-lfs-authenticate 42/doku download
LFS auth alias
Or you can create an git alias which does it automatically for your like this:
lfsauth = !ssh -q $(echo $(git remote get-url --push origin) | sed -E 's/.git$//g' | sed -E 's@:[0-9]+/@ git-lfs-authenticate @g') download >/dev/null && echo "Authentication: $(tput setaf 2)Success\\\\033[00m" || echo "Authentication: $(tput setaf 1)Failed\\\\033[00m"
The requirement for the alias is, that the remote url has the following structure: ssh://
@ : / / .git OR if you are using hosts from your ssh config ssh:// : / / .git
So what is that one doing for you aveter you have added it somehow to your .gitconfig.
- It will run the exectly same command as above, but, it will get all the needed information on its own
- user with servername/domain
- git-lfs-authenticate is added instead of the port
- it will remove the .git from the url to have username and repository name
- and it will add the download string
- the ssh command will be set to quiet and
stdout
ridirected to/dev/null
- it will show success (in green) or fail (in red) to give you the status
- It will create the authentication and keep it open till the tls timeout was reached (app.ini gitea config)
- You dont need to have see it, copy it or something there like
LFS configruration
To small configuration will be automatically added by lfs into your .gitconfig file
[filter "lfs"]
process = git-lfs filter-process
required = true
clean = git-lfs clean -- %f
smudge = git-lfs smudge -- %f
LFS Setup server side
If you are run a coderevision platform like gitea, gitlab, … you need to enable the lfs supportin general first. If you dont do that, the repos will not allow you to use git lfs commands
LFS Setup client side
To enable the lfs support for an repository, you have to install it
$ cd ~/git/mynewlfsproject
$ git lfs install
LFS Add content
To add files to the lfs you can use the parameter track
to create filters on the file namings.
$ git lfs track "*.png"
$ git lfs track "*.jpg"
$ git lfs track "*.pdf"
LFS local FS config
After you have added some filters to the lfs, you will see that the file .gitattributes
was generated, or adopted.
For example like that:
$ cat .gitattributes
*.png filter=lfs diff=lfs merge=lfs -text
*.jpg filter=lfs diff=lfs merge=lfs -text
*.pdf filter=lfs diff=lfs merge=lfs -text
These file should be added, commited and pushed so that all other clients who are working with the repository are getting the same configuration
LFS Push
If you are done with the installation and small configuration, you can just perform your git push commands.
$ git lfs push origin master
Uploading LFS objects: 100% (1/1), 8.0 MB | 0 B/s, done.
LFS enable locking support
If you get during the push the message
Locking support detected on remote "origin".
You can run the command (shown in the mesage anyway) to add the lockverify into your git repo config
$ git config lfs.https://domain/repoowner/repo.git/info/lfs.locksverify true
Or you can perform this command which will apply the same for you:
$ git config lfs.$(git lfs env | grep -o -E "Endpoint=.*lfs " | cut -f1 -d\ | cut -f2 -d=).locksverify true
This one can be also used as an git alias of course
lfsconflock = !git config lfs.$(git lfs env | grep -o -E "Endpoint=.*lfs " | cut -f1 -d\\\\ | cut -f2 -d=).locksverify true
LFS show last logs
To view the last log you got from lfs, you can use the git lgs logs
:
$ git lfs logs last
Errors
File download errors
If you see the following:
$ git pull
Created autostash: 453e055
Downloading [path]/[file] (8.0 MB)
Error downloading object: [path]/[file] (cbe8b0a): Smudge error: Error downloading [path]/[file] ([id]): batch request: Forgejo: Unknown git command: exit status 1
Errors logged to '[localpath]/.git/lfs/logs/[date.time].log'.
Use `git lfs logs last` to view the log.
error: external filter 'git-lfs filter-process' failed
fatal: [path]/[file]: smudge filter lfs failed
You might want to do the following to sort out the issue with smudge:
- get the url to clone again
- re-run the lfs installation with the parameter
--skip-smudge
- clone the repo again
$ git remote get-url origin
ssh://<domain>/path/to/repo.git
$ cd ..
$ rm -f ./<repo>
$ git lfs install --skip-smudge
$ git clone <repo_url>
LFS locking API not supported
If you get the message Remote "origin" does not support the Git LFS locking API
and can not enable it for whatever reason, use the command:
$ git config lfs.https://<domain>/<path>/<reponame>.git/info/lfs.locksverify false
to disable it.
Remove files from commit
To remove one or mor files from a commit which you can go through the followin steps
In this sample we assume that the files got commited to the last commit
Frist perform a soft reset
$ git reset --soft HEAD~
Now that you have the files back in the a staged state, you just need to reset and checkout them
reset
$ git reset HEAD ./file/nubmer/one
$ git reset HEAD ./file2
checkout
$ git checkout -- ./file/number/one
$ git checkout -- ./file2
Last thing is to use commit with ORIG_HEAD
to get back your commit message
$ git commit -c ORIG_HEAD
Now you can push your changes.
If they have been already pushed to a remote repository, you will have to use
--force
with thepush
command.
Remove files from a merge commit
For merge commits it is a bit different as a merge commit is not a regular commit.
To perform that, you have two options:
- Rebase on ancestor commit ID of merge commit
- Direct rebase on merge ID
With ancestor ID
First perform also a interactive rebase, but add the parameter --rebase-merges
like this:
$ git rebase -i --rebase-merges <your ancestor commit ID>
This will open your editor and in there you search for your merge commit e.g. by shot ID or commit msg (in our sample the last merge commit 2223333).
e.g.:
$ git rebase -i --rebase-merges 1112222
#
# inside of your editor
label onto
# Branch test-sh-cli-test
reset onto
pick 1112222 test.sh: cli test
label test-sh-cli-test
# Branch TEST-do-not-merge
reset onto
merge -C 8ccb53f test-sh-cli-test # Merge pull request 'test.sh: cli test' (#75) from asdf_qwere into master
label branch-point
pick 1234123 test.sh: test notif
label TEST-do-not-merge
reset branch-point # Merge pull request 'test.sh: cli test' (#75) from 8140_11913 into master
merge -C 2223333 TEST-do-not-merge # Merge pull request 'TEST: do not merge' (#76) from testing into master
After you found it, insert after right after the merge line (merge -C <merge commit ID>
) a line only containing the word break
or the letter b
.
So it will look like this:
$ git rebase -i --rebase-merges 1112222
#
# inside of your editor
...
reset branch-point # Merge pull request 'test.sh: cli test' (#75) from 8140_11913 into master
merge -C 2223333 TEST-do-not-merge # Merge pull request 'TEST: do not merge' (#76) from testing into master
break
Save and close the file and you will see with git status
that your inateractive rebase is running.
Next, you can perform your changes and after you are done with it, use git commit --amend
to apply the chanes to the commit and continue with the rebase using git rebase --continue
.
Without ancestor ID or direct rebase on merge ID
Assuming, that the merge commit is the last commit in the git history
If you don’t have a ancestor commit ID and you have to perform your action directly on the merge commit, use the merge commit ID to perform your interactive rebase.
$ git rebase -i 2223333
#
# inside of your editor
noop
Again you will get your editor opened and instead of putting the break below your merge commit it will be now at the beginning of the file above the noop
line we want to add the word break
or the letter b
:
$ git rebase -i 2223333
#
# inside of your editor
break
noop
Save-close the file and start to perform your change(s).
After you are done with your change(s) continue with git commit --amend
to add the changes to the commit itselfe and finish your rebase using git rebase --continue
.
Create and apply patches
Create patches
To create a Git patch file, you have to use the git format-patch
command, specify the branch and the target directory where you want your patches to be stored.
$ git format-patch <branch> <options>
The git format-patch
command will check for commits that are in the branch specified but not in the current checked-out branch.
As a consequence, running a git format-patch
command on your current checkout branch won’t output anything at all.
If you want to see commits differences between the target branch and the current checked out branch, use the git diff
command and specify the target and the destination branch.
$ git diff --oneline --graph <branch>..<current_branch>
* 391172d (HEAD -> <current_branch>) Commit 2
* 87c800f Commit 1
If you create patches for the destination branch, you will be provided with two separate patch files, one for the first commit and one for the second commit.
For example, let’s say that you have your master
branch and a feature
branch that is two commits ahead of your master branch.
When running the git diff
command, you will be presented with the two commits added in your feature branch.
$ git diff --oneline --graph master..feature
* 391172d (HEAD -> feature) My feature commit 2
* 87c800f My feature commit 1
Now, let’s try creating patch files from commits coming from the master branch.
$ git format-patch master
0001-My-feature-commit-1.patch
0002-My-feature-commit-2.patch
You successfully created two patch files using the git format-patch
command.
Create patch files in a directory
As you probably noticed from the previous section, patch files were created directory in the directory where the command was run. This might not be the best thing because the patch files will be seen as untracked files by Git.
$ git status
Untracked files:
(use "git add <file>..." to include in what will be committed)
0001-My-feature-commit-1.patch
0002-My-feature-commit-2.patch
In order to create Git patch files in a given directory, use the git format-patch
command and provide the -o
option and the target directory.
$ git format-patch <branch> -o <directory>
Back to our previous example, let’s create Git patch files in a directory named patches
.
This would give us the following command
$ git format-patch master -o patches
patches/0001-My-feature-commit-1.patch
patches/0002-My-feature-commit-2.patch
In this case, we provided the git format-patch
will a local directory but you can provide any directory on the filesystem out of your Git repository.
Create patch from specific commit
In some cases, you are not interested in all the existing differences between two branches.
You are interested in one or two commits maximum.
You could obviously cherry-pick
your Git commits, but we are going to perform the same action using Git patches.
In order to create Git patch file for a specific commit, use the git format-patch
command with the -1
option and the commit SHA.
$ git format-patch -1 <commit_sha>
Copy the commit SHA and run the git format-patch
command again.
You can optionally provide the output directory similarly to the example we provided in the previous section.
$ git format-patch -1 87c800f87c09c395237afdb45c98c20259c20152 -o patches
patches/0001-My-feature-commit-1.patch
Create patch from specific uncommited file
File is staged
If the file is staged already, you can use one of the commands:
--staged
is a synonym for--cached
$ git diff --no-color --cached > 0001-My-feature-staged-change-1.patch
$ git diff --no-color --staged > 0001-My-feature-staged-change-1.patch
File is unstaged
If the file is still unsatged, use that command:
$ git diff --no-color > 0001-My-feature-unstaged-change-1.patch
Apply patches
Now that you have created a patch file from your branch, it is time for you to apply your patch file.
In order to apply a Git patch file, use the git am
command and specify the Git patch file to be used.
$ git am <patch_file>
Referring to our previous example, make sure to check out to the branch where you want your patch file to be applied.
$ git checkout feature
Switched to branch 'feature'
Your branch is up to date with 'origin/feature'.
Now that you are on your branch, apply your Git patch file with the git am
command.
$ git am patches/0001-My-feature-commit-1.patch
Applying: My feature commit 1
Now, taking a look at your Git log history, you should see a brand new commit created for your patch operation.
$ git log --oneline --graph
* b1c4c91 (HEAD -> feature) My feature commit 1
When applying a Git patch, Git creates a new commit and starts recording changes from this new commit.
Troubleshooting patch
In some cases, you might run into errors when trying to apply Git patch files. Let’s say for example that you have checked out a new branch on your Git repository and tried to apply a Git patch file to this branch. When applying the Git patch, you are running into those errors.
file already exists in index
This case is easy to solve : you tried to apply a Git patch file that contained file creations (say you created two new files in this patch) but the files are already added into your new branch.
In order to see files already stored into your index, use the git ls-files
command with the –stage
option.
$ git ls-files --stage <directory>
100644 eaa5fa8755fc20f08d0b3da347a5d1868404e462 0 file.txt
100644 61780798228d17af2d34fce4cfbdf35556832472 0 file2.txt
If your patch was trying to add the file
and file2
files into your index, then it will result in the file already exists in index
error.
To solve this issue, you can simply ignore the error and skip the patch operation.
To skip a Git patch apply operation and ignore conflicts, use git am
with the –skip
option.
$ git am --skip
error in file
In some cases, you might run into some merging
errors that may happen when applying a patch.
This is exactly the same error than when trying to merge one branch with another, Git will essentially failed to automatically merge the two branches.
To solve Git apply merging errors, identify the files that are causing problems, edit them, and run the git am
command with the –continue
option.
$ git am --continue
Change Author of pushed commit
This affects all contributers who area also working or at least have cloned the repository, make sure you really need to do that
Sometimes you have to do noti things, for example chaning the author, or you just maybe pushed it with your wrong git config.
To change an author, you just have to do two small things, a interactive rebase
and a commit ammand
like that:
Use the parent commit ID from the commit you want to change.
$ git rebase -i 6cdf29a
Now you just navigate to your commit and change it from pick
to edit
It will show you then something like:
Stopped at 6cdf29a... init
You can amend the commit now, with
git commit --amend '-S'
Once you are satisfied with your changes, run
git rebase --continue
Next step is to run the git commt amand
with the correct Authorname and mailaddress
$ git commit --amend --author="Author Name <email@address.com>" --no-edit
[detached HEAD a04039e] init
Date: Fri Aug 04 14:00:29 2021 +0200
1 files changed, 2 insertions(+)
create mode 100644 test/my_change_author_test_file
Now we are nearly done, continue with the rebase
$ git rebase --continue
git rebase --continue
Successfully rebased and updated refs/heads/master.
And force push
your changes to the upstream repository
$ git push origin master --force
+ 6cdf29a...a04039e master -> master (forced update)
Extentions
Here you can find some practical additions which can be used in combination with git or makes your live easier with git. Some of these extentions have already there own documentation here and will be just listed(linked) and you can read the documentation there. Also extentions does not only to be plugsins/hooks and so on, also additional applications will be listed here.
Alrady documented additions/extentions:
- tig: An small aplication to view git log/diffs/comits
- <need to be generated ;) >
VIM
vim
of course offers you a lot of fancy and shiny plugins to help you working with git.
But not everything needs to be a plugin, vim
on its own is able to do a lot of nice things which can help you too.
Color highlight in commit message dialog
Let’s use this as a sample, to visualize the best practice for git comit messages, you can add 3 lines into your vimrc
config and get some handy addition applied.
autocmd FileType gitcommit set textwidth=72
autocmd FileType gitcommit set colorcolumn+=72
autocmd FileType gitcommit match Error /\v%^[a-z]%<2l.*|%>50v%<2l.*|%>72v%>2l.*/
What are the above lines about
textwidth
: Maximum width of text that is being inserted. A longer line will be broken after white space to get this width. A zero value disables this.colorcolumn
: Is a comma separated list of screen columns that are highlighted with ColorColumn hl-ColorColumn.match Error /\v%^[a-z]%<2l.*|%>50v%<2l.*|%>72v%>2l.*/
:Error
: Uses the highlighting groupError
%^[a-z]%<2l.*
: This will color the full first line if the first letter is not an uppercase letter%>50v%<2l.*
: This will color everything which comes after 50 characters at the first line%>72v%>2l.*/
: This will color everything which comes after 72 characters on all other lines
Dangling Commits Tags Trees and Blobs
Dangling commits are commits without having a reference and this means that they are not accassable via the HEAD history or any other history of other branches.
How can a dangling commit happen
This can happen if you have a branch which contains some or just one commit and the branch reverence gets deleted without merging the changes into the master/main branch.
$ git switch -c new_branch_for_dandling
Switched to a new branch 'new_branch_for_dandling'
$ echo "Asdf" > file_to_produce_dangling
$ git add file_to_produce_dangling ; git commit -m "will be dandling" ; git push branch
Pushing to ssh://....
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
.
.
.
* [new branch] HEAD -> new_branch_for_dandling
updating local tracking ref 'refs/remotes/origin/new_branch_for_dandlin
Now we have a new commit on a new branch:
$ git log -n 3
| 18bf526 2023-03-07 G (HEAD -> new_branch_for_dandling, origin/new_branch_for_dandling) will be dandling
* 434f285 2022-01-26 N (origin/master, origin/HEAD, master) Merge pull request 'TEST: do not merge' (#76) from testing into master
|\
| * 336d0c2 2022-01-26 G test.sh: test notif
|/
|
...
$ git branch -a | grep new_branch
* new_branch_for_dandling 18bf526 will be dandling
remotes/origin/new_branch_for_dandling 18bf526 will be dandling
So if we now remove the branch it self we will create the dalingling commit:
$ git switch master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
$ git push origin :new_branch_for_dandling
remote: . Processing 1 references
remote: Processed 1 references in total
To ssh://...
- [deleted] new_branch_for_dandling
$ git branch -D new_branch_for_dandling
Deleted branch new_branch_for_dandling (was 18bf526).
Lets have a check on the remote repository now and see what we got there:
$ git fsck --full
Checking object directories: 100% (256/256), done.
Checking objects: 100% (2058/2058), done.
dangling commit 18bf52608606535fc9d2d1c91d389a69e86a2241
Verifying commits in commit graph: 100% (1183/1183), done.
This is now a very simple one and easy to recover as it still has a valid parrent, but just emagine, that your parrent was a branch which got removed and nobody rebased your dangling change ontop of something different.
Detect dangling commits
A common way to do so is to perform git fsch --full
which will validate the connectivity and validity of the objects in your database.
So you could get something like this:
$ git fsck --full
Checking object directories: 100% (256/256), done.
Checking objects: 100% (2058/2058), done.
dangling commit 18bf52608606535fc9d2d1c91d389a69e86a2241
Verifying commits in commit graph: 100% (1183/1183), done.
But it can happen, that you dont see them on your local (checked out) repository and you only see them on the remote one (e.g. on the bare repo). This is for example one of the reasons why version control systems like (forgejo,gitea,gitlab,…) are performing health checks over your repositories to detect such cases.
Another possible case to detect it (if it is only on remote side) that you get such a message while you pull updates from your remote repository:
$ git pull
fatal: invalid parent position 0
fatal: the remote end hung up unexpectedly
This can indicate to you that there are dangling commit(s) on the remote repository where git is not able to download them.
Dealing with dangling commits
You can get the commit ID’s from the
git fsck
commands as shown above.
Recovering dangling commits
Of course you have several ways in git to get things back, lets assume you can sill access the commit:
git rebase <dangling-commit-id>
: lets rebase it on mastergit merge <dangling-commit-id>
: merge it to the mastergit cherry-pic <dangling-commit-id>
: picking it to the mastergit checkout <dangling-commit-id>
: directly checkout the commit- and many others
Now lets asusme you can not access the commit, as we dont get it from the remote repo, but as long as you are somewho able to access the data via the file system, you can recover it:
git cat-file <commit|tag|blob|directory> <dangling-commit-id>
: this will give you some data of the commit, like author, message and so ongit diff -p ..<dangling-commit-id>:
will give you the changes compared to the current status as a patch file contentgit show <dangling-commit-id>
: shows metadata about commit and content changes
Delete all dangling commits
Before you run this two commands, make sure that you really don’t need them any more!
$ git reflog expire --expire-unreachable=now --all
$ git gc --prune=now
The first command will enable you to remove all the dangling commits performed in the past.
Mark from
man git-reflog
:The
expire
subcommand prunes older reflog entries. Entries older than expire time, or entries older than expire-unreachable time and not reachable from the current tip, are removed from the reflog. This is typically not used directly by end users — instead, see git-gc(1).
--all
: Process the reflogs of all references.
--expire-unreachable=<time>
: Prune entries older than<time>
that are not reachable from the current tip of the branch. If this option is not specified, the expiration time is taken from the configuration settinggc.reflogExpireUnreachable
, which in turn defaults to 30 days.--expire-unreachable=all
prunes unreachable entries regardless of their age;--expire-unreachable=never
turns off early pruning of unreachable entries (but see--expire
).
The second one will removed the before pruned commits.
Mark from
man git-gc
:
--prune=<date>
: Prune loose objects older than date (default is 2 weeks ago, overridable by the config variablegc.pruneExpire
).--prune=now
prunes loose objects regardless of their age and increases the risk of corruption if another process is writing to the repository concurrently; see “NOTES” below.--prune
is on by default.
Rewrite history by using difs of changes
This is very usefull in situations where you have removed some lines of code, but you need to restore them and even change them.
Someone could argue, that you can of course create a revert commit or just fully reset and re-checkout the commit to have a clean start again, but what if you have to do that in a bigger file where on several lines the change has to be performed and that maybe not only in one file.
There this comes very hand and can help you dealing with it.
The idea behind it is, that you first go to the commit where you removed the lines of code and there we perform a reset of the commit it selfe, but we keep the change in unstaged.
Because as they are unstaged, we can use the advantage of the interactive mode from git add
. Left have a short look at it.
Lets assume you are already at the commit you have to be and have performed a reset of the current commit (see above how
git rebase
andgit revert
works)
We start with git add --interactive
and use the submodule patch
. This will allow us to select the file which we want to action on. To select a file, simply type the number from the beginning of the line and hit enter.
This will continue until you only press the ENTER key.
$ git add --interactive
staged unstaged path
1: unchagned +0/-13 vars/config.yml
*** Commands ***
1: status 2: update 3: revert 4: add untracked
5: patch 6: diff 7: quit 8: help
What now> 5
staged unstaged path
1: +4/-0 +0/-17 vars/config.yml
Patch update>> 1
staged unstaged path
* 1: +4/-0 +0/-17 vars/conf.yml
Patch update>>
So as mentioned, we have now selected the file and press now ENTER which will display the path of the first hunk form the unstaged changes and give us a choice of actions what we can take:
diff --git a/vars/config.yml b/vars/config.yml
index 1111111..2222222 100644
--- a/vars/config.yml
+++ b/vars/config.yml
@@ -34,15 +34,6 @@
34 ⋮ 34 │ name: "Awesome user17"
35 ⋮ 35 │ 42018:
36 ⋮ 36 │ name: "Awesome user18"
37 ⋮ │- disable: true
38 ⋮ │- 42019:
39 ⋮ │- name: "Awesome user19"
40 ⋮ │- 42020:
41 ⋮ │- name: "Awesome user20"
42 ⋮ │- 42021:
43 ⋮ │- name: "Awesome user21"
44 ⋮ │- 42022:
45 ⋮ │- name: "Awesome user22"
46 ⋮ 37 │ 42023:
47 ⋮ 38 │ name: "Awesome user23"
48 ⋮ 39 │ 42024:
(1/1) Stage this hunk [y,n,q,a,d,e,?]?
If you press here the key
?
and hit enter, you will get a small help displaying what which key refers to:(1/1) Stage this hunk [y,n,q,a,d,e,?]? ? y - stage this hunk n - do not stage this hunk q - quit; do not stage this hunk or any of the remaining ones a - stage this hunk and all later hunks in the file d - do not stage this hunk or any of the later hunks in the file e - manually edit the current hunk ? - print help
I what we ant to, is the edit mode of the hunk, so we type e
and confirm it with ENTER.
What will happen now is, that your editor opens and allows you to chagne the diff which was shown before like so (if you use the one and only vim, just kidding youse what ever you like as log it is not the MS-Editor):
# Manual hunk edit mode -- see bottom for a quick guide.
@@ -34,19 +34,6 @@
name: "Awesome user17"
42018:
name: "Awesome user18"
- disable: true
- 42019:
- name: "Awesome user19"
- 42020:
- name: "Awesome user20"
- 42021:
- name: "Awesome user21"
- 42022:
- name: "Awesome user22"
42023:
name: "Awesome user23"
# ---
# To remove '-' lines, make them ' ' lines (context).
# To remove '+' lines, delete them.
# Lines starting with # will be removed.
# If the patch applies cleanly, the edited hunk will immediately be marked for staging.
# If it does not apply cleanly, you will be given an opportunity to
# edit again. If all lines of the hunk are removed, then the edit is
# aborted and the hunk is left unchanged.
Now lets start editing as you which and save+close it. From now it depends, if you have selected more files or the file contains more hunks, it will continue with asking what it should do and depending on your needs you just act.
When you are done with edeting all the changes, you will be brought back to this view:
*** Commands ***
1: status 2: update 3: revert 4: add untracked
5: patch 6: diff 7: quit 8: help
What now>
Just close it using 7
or q
and confirm it with ENTER.
Now you will see that you have staged and unstaged changes in your repository.
The staged changes, are the changes which you have performed inside your editor and the unstaged ones are the old changes which you probably don’t need any more (to be save vadate it, not that something important got lost).
Now to get your new changes into the commit, use the following command to get your original commit message back:
$ git commit --reuse-message=ORIG_HEAD
# or
$ git commit -C ORIG_HEAD
If you had to perform a rebase first, don’t forget to continue your rebase, if it was the last commit anyway, just perform a force push and you are done.