How to use Git (on Windows)
Stop icon Still Work-in-Progress!
Needs to be revised and reduced more.

I’m glad if parts of it are helpful for others, but that’s not its raison d’Γͺtre: It’s primarily meant to be my personal Git (Reference) cheatsheet/knowledge base, i.e. a collection of notes for myself on how to use Git!
(These tipps were orignally based on my older notes in a text file on my PC, from around 2020-2022…)
(And by the way: I’m (still) using primarily Microsoft Windows.)

It is not meant as a general introduction or comprehensive reference for this tool. For that, more helpful and more general documentation is available on the net; for example:

Contents

Installation & Setup

Although I’ve installed Git many times by now, the time span between each occasion is significant enough that I forget the choices I made the last time, which makes me stumble the next time: “Hm, what do I usually pick here?” πŸ€” (usually it needs only to be done for a new machine/installation every couple of years).

And while the setup its not too complicated, it would be helpful to have a brief reminder; thus this section now (on 2025-11-01, for Git version 2.51.2.windows.1).

  1. Get Git for Windows:
    Standalone Installer: Git for Windows/x64 Setup

  2. The Installation Wizard will show a few (multiple choice) dialogues, e.g. for selecting components.
    I stick mostly with the default, but at some junctions, I make some adjustments.
    Only noteworthy decisions are documented here!

    1. On “β˜‘ Windows Explorer integration”: Uncheck “☐ Open Git Bash here”
    2. Uncheck “☐ Associante .sh files to be run with Bash”
    3. Default editor: Change the default (“Vim”) and switch either to “Notepad++” or “Select other editor as Git’s default editor” (e.g. C:\Program Files\Microsoft Edit\edit.exe).
    4. On “Name of initial branch”: Keep the default (“Let Git decide”), which is currently still set to “Master”.
    5. On “Choosing SSH exe”: If already installed: Choose PuTTY’s plink.exe.
      Otherwise choose “Use bundled OpenSSH” and change it later to PuTTY’s plink.exe
    6. On “Line endings”: Keep the default: “Checkoput windows-style, commit unix-style” (core.autocrlf = true)
    7. On “Terminal emulator”: Change it to “β˜‘ Use Windows’ default console window”
    8. On “Credential helper”: Change it to “β˜‘ None”

But regardless what one chooses here: It can be changed again, anyways by the ways of Git configuration


Configuration

Configuration files

Git provides three different scopes for storing configuration data.
And probably just to make life more complicated than neccessary, the configuration files are named differently in each scope…
Furthermore, Windows Git config files are each stored in different locations.

Order Scope Filename Location (under Windows)
1 System gitconfig Program folder (e.g. C:\Program Files\Git\mingw64\etc\gitconfig).
2 Global .gitconfig User’s local profile or home directory (e.g. C:\Users\Sascha\.gitconfig).
3 Local config Inside the hidden .git directory of the repository.

The scopes are cascading, so the system scope trumps the global scope; and the global scope trumps the local scope:

( system ( global ( local ) ) )

The global Git configuration file (.gitconfig) will only be created the first time it is used.
Even just asking Git to edit the file will force its creation:

git config --global --edit

To get a list of all settings; apply --show-origin to also show in which file each value is set; type q to exit:

git config --list
git config --list --show-origin

To get only the current value of a specific key, use git config <key>.
For example:

git config user.name
Note:
Git configuration variables can’t be set by the repository you are cloning, to prevent the execution of arbitrary code.

Basic Setup

To reset any config item back to is default value, simply use --unset; for example:

git config --global --unset user.name

User identity

git config --global user.name "Sascha Offe"
git config --global user.email "id@example.net"

Text editor

In the following example, it’s set to use Notepad++ (see also Command Line Options):

git config --global core.editor "'C:/Program Files (x86)/Notepad++/notepad++.exe' -multiInst -notabbar -nosession -noPlugin"

By the way: To use the classic/native(?) backslash-path-separator (\), you’ll need to escape it with another backslash: \\
Example: "C:\\Program Files\\Notepad++\\notepad++.exe" -multiInst -notabbar -nosession -noPlugin

Alternative: Microsoft recently released Edit, a new and Open Source text editor for the terminal; with a TUI inspired by the old MS-DOS 5.0 Editor (of which I have very fond memories). I’ll try this one out for a while:

git config --global core.editor "`"C:/Program Files/Microsoft Edit/edit.exe`""

And another tip: If you want/need quotation marks (due to spaces in the path): Escape them (in Powershell!) with the backtick symbol:
Example: git config --global core.editor "`"C:/Program Files/Microsoft Edit/edit.exe`""

Ignore items in all repositories

If you always want to ignore the same kind of files and folders, you can define a global ignore file that will be applied to all repositories on your system (next to the local, repo-specific .gitignore file).

The file and its content must be created in the same manner, but name and location is up to you. I prefer something like .gitignore_global and save it (for maintainability reasons) in one of my repositories.

git config --global core.excludesfile C:\<path>\.gitignore_global

Default branch name

By default Git will create a branch called master when you create a new repository with git init.
From Git version 2.28 onwards, you can set a different name for the initial branch.

In the following example, it’s set to name it main:

git config --global init.defaultBranch main

Aliases

You can create aliases (to shorten or customize common actions), which then can be invoked by

git <Alias>

For examle: Define slog as an alias for calling log with specific parameters:

Setup an alias for a Git command (for example to provide certain options by default, e.g. always using ISO 8601 based timestamps in log output).

git config --global alias.slog log

That will add an entry under the alias section in the global .gitconfig file, which you can further refine by opening the configuration file in a text editor – git config --global --edit – and then find the section there; for example:

[alias]
    slog = log --oneline --pretty=format:"%h%x09%an%x09%ad%x09%s" --date=iso

More ideas:

git config --global alias.c commit
git config --global alias.co checkout
git config --global alias.st status

GUI Tools

Despite the fact that my main motivation for this cheatsheet is to have a collection of often-used native Git commands for the Command Line Interface (CLI) at hand, and idea of not being reliant on other (graphical, external/third-party tools, that may change or disappear), it is sometimes, for some tasks, a good idea to use a GUI.


Git Repository

The git init command creates a new, fully functional, version controlled repository; Git doesn’t require any pre-existing server or admin privileges.

Some terminology clarification before we continue:

A Git repository comes in two different flavours (nice comparison/description):

Normal repository β†’ for working
A normal git init creates everything you need to support a full-featured, Git-based environment, along with a special spot known as the working tree (aka working directory), where you can create files and write code.

Main points:

  • The files and folders at the top/root level represent the working tree/directory (where a developer actively edits and updates files that Git tracks).
  • The hidden .git directory in the top/root level contains the Git meta-data for this repository (e.g. the history of all commits across all branches).
Bare repository β†’ for sharing
If you only want to host/manage a Git repository (similar to what GitHub, BitBucket, GitLab etc. offer), you create a bare Git repository with git init --bare.

A bare Git repository has all the capabilities of a normal (non-bare) repository, with the exception that it can’t be used to work in it:
It is intended to be used as a remote repository, where code is shared (between members of a team), but it’s not intended for local development!
Users can push to and pull from it, but can’t use it for ongoing development (local commits, branch creation, etc.); for that, a normal repo with a workspace is needed.

If you inspect a bare Git repo, you’ll notice that it’s missing the (hidden) .git folder of a normal repository. Instead, all that meta-data content is right there in the root directory (with some differences between both repo types, but that’s not important now).

Main points:

  • Contains only the Git meta-data itself at the root level (in a normal Git repository stored in a hidden .git subdirectory).
  • Doesn’t have a working tree (and can’t have a working tree).
  • Doesn’t have a default remote origin repository: Git assumes that it will serve as the origin repository for remote users, so it doesn’t create a default remote origin (= pull and push operations won’t work here, since Git assumes that you don’t intend to commit any changes directly into this repository).
  • Typically used as a central place for exchanging data with others (by pushing to and pulling/fetching from it).
  • By convention(!), the name has usually a *.git suffix (e.g. <ProjectName>.git).

A brief comparison of the file structure of both repo types:

Initialized empty normal Git repository Initialized empty bare Git repository
> git init project

project/
    .git/
        <Git files & directories>
        ...
    <User files & directories>
    ...

The .git folder is hidden in this case!
The user files & directories represent the working copy of e.g. a checked out “Master” branch

> git init --bare project.git

project.git/
    <Git files & directories>
    ...

Create a bare repository

The most common use case for a bare repository is to create a remote central repository.

Central repositories should always be created as bare repositories, because pushing branches to a non-bare repository has the potential to overwrite changes.

Think of it as a way to mark a repository as a storage facility, as opposed to a development environment.

This means that for virtually all Git workflows, the central repository is bare, and developers local repositories are non-bare.

git init --bare [<directory>]

The --bare flag creates a repository that does not have a working directory, which makes it impossible to edit files and commit changes in that repository.

You would create a bare repository to push and pull from other repositories, but never directly commit to it.

By convention, the name of repositories initialized with the --bare flag should end with .git:

git init --bare MyProject.git

[TODO 1]

Note that in this case you’ll need to first allow people to push to your repository. When inside test_repo.git, do

git config receive.denyCurrentBranch ignore

As commented by prasanthv, this is what you want if you are doing this at work, rather than for a private home project.

git init --bare --shared=group

You can also add the –shared option for init if you plan on having other people push to this repo. It automatically adds group write permissions to the repository

[TODO 2]

Create a bare repo (ie git init --bare MyProject.git) on the network drive (if not already have done that).

Add the new bare repo as a remote repo to your project repo (of you not already have done that, ie

git remote add origin <path to bare repo>)

and push all commits to the new remote repo (ie git push --mirror origin).

On the other computor you can now clone the new remote bare repo from the network drive (ie git clone <path to bare repo>)


git remote add origin //remoteServer/git/Share/Folder/Path/MyGitRepo1 git push origin master

Create a local repository

“Creating a new local repo” simply means applying the init command, so that a hidden .git subdirectory will be generated by and for Git.
No files or directories will be tracked by Git by default!

git init [<directory>]

This adds a .git subdirectory to the current or the specified directory and makes it possible to start recording revisions of the project.

(Running git init on a directory that already contains a .git subdirectory is harmless: It will not override an existing .git configuration.)

This is now the checkout (or working copy) of the repo.
Its content can be either untracked (default) or tracked by Git.

More…


Clone a Git Repository

Clone a repository, in either a new subdirectory (named like the original repository);
or into a specfic directory (supplied by name or path):

TODO - Using use . for the current working for “Path\to\a\RepoCloneDir”

Via SSH (Example: From BitBucket.org):
git clone xxx@yyyyyyyyy.zzz:abc/<RepoName>.git [Path\To\Local\RepoCloneDir]
    
git clone git@bitbucket.org:<username>/<RepoName>.git [Path\To\Local\RepoCloneDir]
Via HTTPS:
git clone https://yyyyyyyyy.zzz/abc/<RepoName>.git [Path\To\Local\RepoCloneDir]

Only a specific tag or branch

As above, but with the parameter branch specify, which branch (or tag) you only want to get:

git clone --branch <tag|branch> [... as usual ...]

Shallow clone (limited history)

Clone the repository from Repo_URL, but only with the commit history specified by the option depth.

Shallow cloning is useful when one is working with a huge repository that has an extensive commit history, but of which you don’t need all of right now.

In this example, only the most recent commits (depth level 1; --depth=1) are included in the new clone:

git clone --depth=<level> <Repo_URL>

Information about/from a Git Repository

Other commands ([TODO] not yet mentioned below):

git log --graph --decorate --pretty=oneline --abbrev-commit --all
git branch
git tag

General infos

For example the Fetch- and Push-URL, the branch name, …:

git remote show origin

Status

git status
git status -s | --short

Differences (changes/modifications) in files

git diff
git diff --staged

Commit Log

By default, git log shows the commits made in that repository in reverse chronological order (i.e. the most recent commits first).
The order can be switched with --reverse, but one can’t simply combine it with -n 3 to only show the first three commits.

git log -n 3  # Show the latest three commits    
git log -3    # Same
git slog -3   # Same, but in short form (single line)

Latest Git commit from a branch

git log -n 1                         # Current branch
git log -n 1 origin/master
git log -n 1 some_local_branch
git log -n 1 --pretty=format:"%H"    # Only the hash value of the commit

Source: Command to get latest Git commit hash from a branch (StackOverflow, 2013)

Pretty Format

More on Git Log: Pretty Formats

x y
%h abbreviated commit hash
%x09 tab (character for code 9)
%an author name
%ad author date (format: respects the --date= option)
%s subject
(file) There is no placeholder for the filename; workarounds:
Use --name-only or --name-status or --stat
(but that won’t be on a single line!)
> git log -n 1 --oneline --pretty=format:"%h%x09%an%x09%ad%x09%s" --date=iso

501644b Sascha Offe     2025-06-09 19:49:25 +0200       Minor tweak

> git log -n 2 --oneline --pretty=format:"%h%x09%an%x09%ad%x09%s" --date=iso --name-only

65a3fbe Sascha Offe     2025-11-01 21:31:27 +0100       Improvements regarding the parameters for and the handling of colors
powershell/Modules/saoe/public/Miscellaneous/Write-saoeHost.ps1

0bbfc39 Sascha Offe     2025-10-31 21:43:06 +0100       Custom version of Write-Host with more font & color styling capabilities
powershell/Modules/saoe/public/Miscellaneous/Write-saoeHost.ps1

> git log --stat --oneline --pretty=format:"%h%x09%an%x09%ad%x09%s" --date=iso

d5d24c9 Sascha Offe     2025-10-26 14:25:17 +0100       Updates because the URL of the remote Git repository changed
 content/blog/hotkey-1.0.md        |  7 ++-----
 content/blog/journal-11.md        |  4 ++--
 content/blog/journal-18.md        | 39 +++++++++++++++++++++++++++++++++++----
 content/page/software-overview.md | 20 ++++++++++----------
 4 files changed, 49 insertions(+), 21 deletions(-)
 
...

> git log --graph --decorate --oneline

* 81479d0 (HEAD -> master, origin/master, origin/HEAD) Latest commit message...
* 64fbc25 Previous commit message...
...

Example (Powershell Gridview)

This example uses the slog alias (see above) to get short log lines; those will be converted to CSV objects and then handed over to Out-Gridview for displaying it in a window:

git --no-pager slog | ConvertFrom-Csv -Delimiter "`t" -Header "Short Hash","Author","Timestamp","Subject" | Out-GridView -Title "Git Log"

Commits between X and Y

Where X and Y can be a tag, HEAD or a Commit ID.
X and Y must be in ascending order, i.e. <older>..<newer> – the other way around doesn’t work!

git log --pretty="%h (%an) - %s" <tag-name>..HEAD
git log --pretty="%h (%an) - %s" c3eeab6..0158d99

Regarding double dots (..) and triple dots (...) (it’s complicated: StackOverflow #1, StackOverflow #2 :-/):

Commits for a specific date range

git log --since='<date>' --until='<date>'
git log --since='<date>'
git log                  --until='<date>'

The <date> value can be specified in a variety of formats, like 2025-07-11 or 07/11/2025 or Jul 7 2025; even relative terms are possible, for example yesterday or 2 weeks ago etc.

Gotcha: Stuck in the CLI when using git log

Git Windows Command Prompt gets stuck during Git commands with (END), for example when using git log.

The reason is the use of the unix-y pager command “less” under Windows.
Workaround: Press q to exit the pager.

git --no-pager|-P: Don’t pipe Git output into a pager (a “pager” is something like less or more, which is a bit strange to use on Windows).

Other alternatives to handle the Log: Pipleine the unpaged output to some Powershell cmdlets:

git --no-pager log ... | more
git --no-pager log ... | Out-Host -Paging
git --no-pager log ... | Set-Clipboard
git --no-pager log ... | Out-GridView

Who is responsible? (Who is to blame?)

git blame shows what revision and author last modified each line of a file; has multiple optional parameters to tweak the output.

> git blame .\ReadMe.md
e1b5357b (Sascha Offe 2024-03-10 18:15:12 +0100  1) # Powershell module [...]
0bdc6575 (Sascha Offe 2020-12-29 20:58:38 +0100  2)
476c3e37 (Sascha Offe 2021-01-16 17:34:02 +0100  3) My personal toolkit [...]
...

Forward: <Space>
Exit: q


Add changes to the staging area (index) / Track items

Update the index with the content found in the working tree (prepare/stage the content for the next commit). Directories will be added just like that, including all subdirectories and files in it.

The primary function of the git add command is to promote pending changes in the repo’s working directory to the Git staging area.
By adding items to the index, these item will also change from being untracked to being tracked by Git.

By the way, the options are case sensitive! (I’m looking at you, -A).

git add [options] <file|directory> [<file|directory>] [...]

git add file1.ext file2.ext file3.ext
git add *.x
git add */*.x  # [TODO] What?!

Example: Three versions that all do the same:

Useful options

Short Long Description
-A --all Adds, modifies, and removes index entries to match the working tree (including new, previously untracked files in sub-directories!).
-n --dry-run Don’t actually add the file(s), just show if they exist and/or will be ignored.
-v --verbose Verbose output.
-e --edit Start the editor and show changes in unified diff format.
-f --force Allow adding otherwise ignored files.
-u --update (see below/TODO)
-i --interactive (see below)

Interactive Staging: git add -i

[TODO] -u/–update

Update the index just where it already has an entry matching pathspec. This removes as well as modifies index entries to match the working tree, but adds no new files.

If no pathspec is given when -u option is used, all tracked files in the entire working tree are updated (old versions of Git …).

Remove and modify index entries to match the working tree, but does not add new files!

Add/delete only all tracked modified files to the stage; ignore all yet untracked modified files.


Commit staged changes to the repository

Commit the staged files to the local repository:

git commit -m "Message for the initial commit"

On Commit Messages

Mood & Tense

I was aware, that the common and recommend way of writing commit messages was and is to use to use the imperative mood, in present tense; like “Do X”.
(Tip: Read your commit message with this at the beginning: “If applied, this commit will <do x…>”

But that always felt somewhat wrong to me when typing, probably because I always used version control systems alone, and on codebases, where I am the single developer.

And thus commiting a change happens normally at the end of my develeopment process, when I have already written all the code and comments – so I treat it more like a worklog of what I did do or did accomplish in the hours or days before. For that, the indicative mood in past tense makes more sense.

But I also know that Git originally comes from the world of distributed and decentralized development, by many programmers: The Linux kernel.

On the one hand: These are different circumstances, unlike mine. Why should I care?

On the other hand: After having read now a few more articles and discussions (pro & contra) about this topic (see below), I’m actually sligthly more in favor of imperative, present tense mood…

And also: This is for the subject line; the body of a commit message can be different

Food for thoughts:

Ignore files & folders

Repository scope

Some files and directories should intentionally be ignored by Git and remain untracked (files that are already tracked by Git are not affected):

  1. Create the .gitignore file (in the repository directory).

    New-Item -ItemType File -Name .gitignore # Generating a 'dot' file on Windows it easier with Powershell. 
    attrib +h .gitignore                     # Hide file in the normal file explorer view.
    
  2. Create rules in that file for what should be ignored:
    (Each line in a .gitignore file specifies a pattern)

    • # starts a comment line.
    • Folders must have a trailing slash: build/ (this will ignore a directory with the name “build” anywhere in the file tree).
    • *build*/ ignores all kinds of “build” folders, e.g. “build”, “build32”, “build64”, “_build”, “foo/build”, etc.

    File content example:

    # Ignore all kinds of 'build' folders (build, build32, build64, _build, foo/build, etc.):
    *build*/
    
    # Ignore this file anywhere at or below the directory that contains this .gitignore file:
    CMakeUserPresets.json
    
    # Ignore byte-compiled/optimized/DLL Python files
    __pycache__/
    *.py[cod]
    *$py.class
    
  3. Add and commit this file to the repo, like any regular file.
    (Because a .gitignore file is not added to a repository by default!)

     git add .gitignore
     git commit -m "Add .gitignore file to the repo"
    

Global scope

If you’re is working across multiple projects, there are often files and folders that should always be ignored, (i.e. these rules should apply to all repositories on the system). For that, one can use a global .gitignore file, instead of adding the patterns to each repository’s local .gitignore file individually.
It’s also set up in the same way (see above), but one must point the Git configuration to that file.

Commit change to the staging area/index

1. git add
2. git commit -m "Message"

or in one step
(But new files that are still untracked will be ignored; those need to be explicitly added first!)

git commit -am "Message"    
git commit -a               # Opens an external text editor for the commit message.

“… as a convenient way of reviewing the changes I am about to commit.
With that, only committing part of the changes is no additional effort.

git commit --patch 
git add --patch # **[???]**

Amend change

This will let you add changes to your latest commit & even edit the commit [history] message

If you’ve missed to mention a thing, or forgotten a minor change in a source code file, you can add it retroactively to the latest/most recent commit, before it has been pushed!: (I’m not sure if it’s not technical feasible or not, but at least it’s not recommende to amend a commit that has already been pushed…)

git commit --amend          # Variant A
git commit -a --amend       # Variant B

That opens an external text editor which is prefilled with the most recent commit message. Variant (A) requires that the change has been staged already; use variant (B) to add the lates modification to the stage in one go.

Options:

git commit -a --amend --no-edit
! Careful: Don’t amend commits that have already been pushed, because it modifies the commit history!
One could do it if one works alone or when you’re absolutely sure that nobody has pulled your commit yet.
But even then, you’ll have to use git push --force to overwrite your old, un-amended commit!
Even safer: git push --force-with-lease --force-if-included: This will abort if there are any new changes in the remote repo; this avoids that another one’s pushed commits will be deleted

Undo & Redo stuff

Unstage/Untrack a file

git reset HEAD <file>
git reset -- <file>      - Removes file from the stage/index.

Undo the last commit

Go back to the previous state (HEAD-1).

Soft Reset: Files will remain, uncommited.

git reset --soft HEAD~1

Hard Reset: All files will be deleted:
If you have commited file1, file2 and file3, all three will be deleted from the working directory!

git reset --hard HEAD~1 

Remove a file or directory

Remove a File:

1. git rm file_name          # Remove from local repository and also from working directory

1. git rm --cached file_name # Remove from local repository, but keep copy in the working directory (= untracked file)

Remove a directory:

1. git rm --cached -r directory_name

2. git commit -m "Remove ..."
3. git push origin branch_name 

Note: To remove sensitive data completely, even if already published, one will have to do some special magic (rewriting commit history etc.); look it up if needed (hint: git filter-branch -f –index-filter ‘git rm –cached –ignore-unmatch file.txt’; git forget-blob…) more.

But even if done, it’s highly recommended to change that data (like passwords, SSH keys etc.), since you will never know if it has survived in some out dated clone or fork on someone’s machine!

Reset file to original state (discard latest modifications)

(?) Moves file from the stage (index) back into the working directory.

git checkout -- <FileName>
git checkout HEAD -- <FileName>

Rename or move a file or folder

To preserve Git history, use git mv instead of the OS methods for moving or renaming files. Because otherwise, Git just sees a deleted file (the old one) and a brand-new file (the new one).

  1. Example: (Git has some ways to auto-detect whether the old and new name (or path) may be the same item, one should bet on it…) Update: Hm, maybe it’s not so smart and git mv is just a shorthand -> https://stackoverflow.com/questions/1094269/whats-the-purpose-of-git-mv

     git mv OldName NewName
         (The files are automatically added to the stage; no need to run `git add`!)
     git commit -m "Message..."
    
  2. Example: Move all files from a subdirectory up one level (to the current directory):
    Using a workaround for Windows, when using Powershell

     git mv (gci .\NetInstall\*) .
    

Restore working tree files

Ref/Doc page

git restore path/to/file
git restore --staged path/to/file

Sharing and Updating

Push to Remote Repository

Pushes the currently checked-out branch to the origin (remote).

git push <remote> <branch>
git push                      # without any arguments

So, if git status said you were in the master branch, and git remote -v returned the remote origin, then git push would be the same as git push origin master. (git push alone has supposedly been deprecated for some time, because it is implicit, rather than explicit.)

When pushing on the same branch, one can also use instead:

git push <remote> HEAD

Add a remote with the name “origin”:

git remote add origin git@bitbucket.org:<username>/<RepoNameOnBitBucket>.git

git push                      - If a remote (e.g. BitBucket) was set up; see above.
git push origin branch_name   - ... or explicitly call the remote and branch by name.

Pull or Fetch from Remote Repository

Info icon

An aborted pull is a fetch

If you try to do a git pull operation, but your working directory has uncommitted files, or a copy of the remote files into your workspace would create a merge conflict, the git pull operation short-circuits and turns into a git fetch operation instead.

All of the updates from the remote repository are copied into your local Git repository, but the system leaves your local workspace alone.

This emphasizes the fact that the git pull is really two operations combined into one: the git fetch and the git merge commands. If a developer successfully issues a git fetch and a git merge command right after one another, the result is equivalent to a git pull.

[TODO] https://www.atlassian.com/git/tutorials/syncing/git-pull

[TODO] On Pull & Rebase

  1. Initial situation:

     (RemoteRepo)
          \
           [OtherDev pushed before me into the Repo]
            | 
     B      C
     |      |
     A      A
    
     [I]    [OtherDev]
    
  2. Now I want to push my changes also to the repo… That will be rejected, because I didn’t yet take into account the last changes of OtherDev:

     I do:      git commit -am "foo"  
                git push
    
     Repo says: Rejected!
    
  3. What to do now? If I’d do just a git pull, I would create a new Merge Commit:

                    _
    (RemoteRepo)   /  D } <-------------- merge commit
      "git pull   /   | }
            +---> C   B } My Commit
                  \   | }
                   \_ A }
    
  4. Therefore, do this instead: git pull --rebase

             Desired state:    That makes a "rebase":
    
    (RemoteRepo)  B               Put aside 'B',
       Holt 'C'   |               Get 'C' from the Repo and add it;
             +--> C               only after this 'B' will be attached to 'C'.
                  |
                  A
    

Summary:

Tip: Alias: “git pr” -> “pull –rebase”


Branching and Merging

Branches

List branches

git branch     # List only local branches
git branch -r  # List remote branches
git branch -a  # --all (?) List all branches (local & remote)

Create new local branch, but stay in the currently checked out (e.g. master)

git branch <NewBranch>

git branch <NewBranch> [<StartingPoint>]

Create new local branch and switch to it immediately

git checkout -b <NewBranch>

Switch branch

git checkout <BranchName>

Push a new local branch to a remote Git repository and track it too

  1. (Create branch, see above)
  2. git push -u origin <BranchName>

Delete (close) branch

git branch -d       <BranchToBeDeleted>   : The branch must be fully merged in its upstream branch, 
           --delete                         or in HEAD if no upstream was set with --track or --set-upstream-to.

           -D                             : Force delete the specified branch, even if it has unmerged changes. Shortcut for --delete --force.

Delete (close) branch locally

git branch --delete localBranchName

Delete (close) branch remotely

git push origin --delete remoteBranchName

Then try to synchronize your branch list using:

git fetch -p

The -p flag means “prune”. After fetching, branches which no longer exist on the remote will be deleted.

Merge

  1. Checkout the branch to which the branch should be merged, e.g. ‘master’:

     git checkout master
    
  2. Merge:

     git merge Branch123 -m "Merged Branch123 to master."
    
  3. Might result in a conflict.
    The affected file in the working directory has now some markers:

     <<<<<<< HEAD
    
     Different in Head
    
     =======
    
     something completely different in BranchToBeMerged
    
     >>>>>>> BranchToBeMerged
    
  4. Resolve manually:
    Delete the conflict markers (<<<<<<<, =======, >>>>>>>) and make the changes you want in the final merge; save file.
    Add file to stage again: git add <filename> (that marks the conflict as resolved).
    Conclude the process by a regular git commit (no need to run git merge... again after that).

By the way: A (simple) merge isn’t really shown in any commit history, it seems. There are long explanaition on why/how Git works, but I haven’t delved into it.

Squash multiple commits into one with rebase

Do this on your feature branch. Only squash commits that haven’t been pushed to the repository (are not yet public): Squashing rewrites the history!

git rebase --interactive HEAD~<number_of_recent_commits_you_want_to_squash>

Example:

  1. Start interactive rebase mode to squash the latest three commits: git rebase -i HEAD~3
  2. Editor opens: Make sure the first commit says pick (p) and change the rest to squash (s) (without a “pick”, Git complains that a commit has to happen).
  3. Save and close the editor.
  4. Editor opens: Edit commit message.
  5. Save and close the editor.
  6. Force push the final, squashed commit: git push -f

Tags

Tags specify points in a repository’s history.
There are two types of tags: lightweight and annotated.

Sharing tags with the remote repo

By default, the git push command does not transfer tags to remote servers!

You need explicitly push tags to a shared server after you have created them:

git push <remote> <tagname>

If you have a lot of tags that you want to push up at once
(this will transfer all of your tags to the remote server that are not already there):

git push origin --tags

Now, when someone else clones or pulls from your repository, they will get all your tags as well.

Note: Pushing tags does not distinguish between lightweight and annotated tags; there is no simple option that allows you to select just one type for pushing.

Commit and tag in one command

That is not possible!
Do this instead:

git commit -m "Ready for v1"     # Commit
git tag -a v1.0.0                # Tag (annotated)
git push                         # Push the commit to the remote repo
git push origin master --tags    # Push the tags to the remote repo/branch

View & List tags

Create a tag

Create a tag later

You can also tag commits after you’ve moved past them.

Example: Suppose you forgot to tag the “Update CMake” commit (in the middle) as “v1.5”:

> git log --pretty=oneline

166ae0c4d3f420721acbb115cc33848dfcc2121a Create something
9fceb02d0ae598e95dc970b74767f19372d61af8 Update CMake
964f16d36dfccde844893cac5b347e7b3d44abbc Edit ToDo list
...

To tag that commit, you specify the commit checksum (or a part of it) at the end of the command.
In this example, the first seven characters from the beginning of the commit checksum (9fceb02) are unique enough to identify the commit without a doubt:

git tag -a <tag> <CommitChecksumPart>
git tag -a v1.5  9fceb02

You can then see that the commit is now tagged as “v1.5”:

> git show v1.5

tag v1.5
Tagger: Sascha Offe <id@example.net>
Date:   ...

version 1.5
commit 9fceb02d0ae598e95dc970b74767f19372d61af8
Author: Sascha Offe <id@example.net>
Date:   ...

Update CMake
...

Delete a tag

To delete a tag on your local repository, you can use git tag -d <tag>.

Note that this does not remove the tag from any remote servers.
There are two common variations for deleting a tag from a remote server.

  1. The more intuitive way to delete a remote tag is with git push origin --delete <tag>.

  2. The other method is git push <remote> :refs/tags/<tag> – interprete it as the null value before the colon being pushed to the remote tag name, effectively deleting it.

[???] What about deleting local and then pushing to remote? Does that not work?

Checking out a tag (instead of a branch)

If you want to view the versions of files a tag is pointing to, you can do a git checkout of that tag, although this puts your repository in “detached HEAD” state, which has some ill side effects!

git checkout <tagname>

In “detached HEAD” state, if you make changes and then create a commit, the tag will stay the same, but your new commit won’t belong to any branch and will be unreachable, except by the exact commit hash. Thus, if you need to make changes – say you’re fixing a bug on an older version, for instance – you generally should create a branch:

git checkout -b <new-branchname> <tagname>

If you do this and make a commit, your new-branchname branch will be slightly different than your tagname tag since it will move forward with your new changes, so be careful.


Hooks

Hooks are custom scripts that can invoked when certain events happen.

Languages

Examples come often as Bash, Perl or Python scripts, but you can write a hook in any language, as long as it’s executable on your box (i.e. can be invoked from the terminal/command line) – see also and also.
One could also use the hook script as a wrapper for calling another script.

(Incomplete) List of possible hooks

To enable a hook, just remove the “.sample” from the filename, or create a new file without the “.sample” suffix.

Hook Description
applypatch-msg Allowed to edit the message file in place; can also be used to refuse the commit after inspecting the message file.
commit-msg Called after the user enters a commit message.
fsmonitor-watchman
post-update
post-commit Called immediately after the commit-msg hook
pre-applypatch Can be used to inspect the current working tree and refuse to make a commit if it does not pass certain tests.
pre-commit Executed every time you run git commit before Git asks the developer for a commit message or generates a commit object.
pre-merge-commit Invoked after the merge has been carried out successfully and before obtaining the proposed commit log message to make a commit.
pre-push Can be used to prevent a push from taking place.
pre-rebase Can be used to prevent a branch from getting rebased.
pre-receive
prepare-commit-msg Called after the pre-commit hook to populate the text editor with a commit message.
push-to-checkout
sendemail-validate
update
Further reading:

How to…

Setup a SSH keypair on Windows to authenticate with your repositories

πŸ‘‰ How to set up a SSH key pair with PuTTY on Windows.

Import a local repository to a BitBucket repository

  1. Create a new local Git repository (with some content/files maybe).
    Example:

     C:\devel\git\>         md RepoName
     C:\devel\git\>         cd RepoName
     C:\devel\git\RepoName> git init
     C:\devel\git\RepoName> git add --all
     C:\devel\git\RepoName> git commit -m "Initial Commit"
    
  2. Logon to BitBucket.org and create there also an empty Git repository; that will become the remote/target.
    (See also Importing code from an existing project)

  3. Connect your newly created local repository to the repo on Bitbucket (meaning: Add a remote with the name “origin”).

    Get the clone URL from the BitBucket-Repo page; either SSH (e.g. git@bitbucket.org:<username>/<RepoNameOnBitBucket>.git) or HTTPS (e.g. https://<username>@bitbucket.org/<username>/<RepoNameOnBitBucket>.git)

    I prefer SSH (it’s easier to work with, when PuTTY/Pageant is set up correctly):

     git remote add origin git@bitbucket.org:<username>/<RepoNameOnBitBucket>.git
    
  4. Push local data to remote repository
    (-u: “For every branch that is up to date or successfully pushed, add upstream (tracking) reference […]”)

     git push -u origin main
    

    Assuming that the initial work for using a SSH key is done (see above), there still may show up warning/error messages (see below](#other-known-issues))…

  5. Even if all that worked, you may still get another error:

     ! [rejected]        main -> main (fetch first)
     error: failed to push some refs to 'bitbucket.org:<username>/<RepoNameOnBitBucket>.git'
     hint: Updates were rejected because the remote contains work that you do not
     hint: have locally. This is usually caused by another repository pushing to
     hint: the same ref. If you want to integrate the remote changes, use
     hint: 'git pull' before pushing again.
     hint: See the 'Note about fast-forwards' in 'git push --help' for details.
    

    But the recommend simple “pull” will not help:

     > git pull
     warning: no common commits
     ...
     There is no tracking information for the current branch.
    

    Solution: Since the local repo and the repo on BitBucket don’t have a shared history (yet), this must be explicitly allowed:

     > git pull origin main --allow-unrelated-histories
    

    This will trigger an automatic merge action with default commit message – simply accept/use/do that!

Other known issues


Move file from repo OR split parts of a repo

New (2025-11)

Variant A
Use git-filter-repo: A 3rd party tool/add-on/script collection for Git.
But also acknowledged/recommended by Git!
See also below: I used it already?
Variant B
git filter-branch -> not recommended anymore!
foo/
    bar/
    baz/
    xyz/ <-- We wanna split 'xyz' from the 'foo'-repo

> git clone foo
> git filter-branch --prune-empty --subdirectory-filter xyz/ -- master
                                                             ^^ a separator-plus-space! Not an argument!

Rewrites the history of all commits, as if only ‘xyz’ ever existed.
The content of ‘xyz/’ move up!
And…? That’s it?!

Variant C
  1. git subtree split --prefix=<path>/<folder> -b <name>
    Creates a new branch “name” with the content of the prefixed folder ("path/folder”)

  2. git init --bare

  3. Back in the source repo: Push to the ‘master’ branch in the new repo:
    git push ../new_repo ???? name/branch??? master

  4. Check

  5. Remove from source repo:

     git rm -r split_dir/
     git commit -m "Remove..."
    

Move a file from one repository to another repository, with preserved Git history

Tested on my two test repositories on 2022-02-27 – Update 2025-11-17: Uh, I did use git-filter-repo already!?

  1. Prerequisites

    The third-party tool ‘git-filter-repo’ is required; it’s recommended by Git itself (instead of git’s own ‘filter-branch’).

    Installation (Windows; run from a cmd shell, not from python!):

     > python -m ensurepip --upgrade                 # If PIP is missing 
     > python -m pip install --user git-filter-repo
    
     "WARNING: The script git-filter-repo.exe is installed in 'C:\Users\Sascha\AppData\Roaming\Python\Python39\Scripts'
     which is not on PATH.
     Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location."
    

    So, add it to the path…

  2. Clone the source repo

    Clone the original source repo, because we will modify/rewrite history, so don’t use the orginal repo! And download the entire repository history:

     C:\devel\git> git clone git@bitbucket.org:<username>/gittestrepo.git gittestrepo_clone
     C:\devel\git> cd gittestrepo_clone
     C:\devel\git\gittestrepo_clone> git pull --all --tags
    

    To be on the safe side, one could now detach this clone from its origin…

     C:\devel\git\gittestrepo_clone> git remote -v
     C:\devel\git\gittestrepo_clone> git remote rm origin
     C:\devel\git\gittestrepo_clone> git remote -v
    

    … but ‘filter-repo’ will do that for you anyway. If you do it before yourself, ‘filter-repo’ will complain:

     Aborting: Refusing to destructively overwrite repo history since
     this does not look like a fresh clone.
       (expected one remote, origin)
     Please operate on a fresh clone instead.  If you want to proceed
     anyway, use --force.
    
  3. Filter out the files that should be moved

     C:\devel\git\gittestrepo_clone> dir                                # For comparision (before/after)
     C:\devel\git\gittestrepo_clone> git filter-repo --path "file4.txt"
     C:\devel\git\gittestrepo_clone> dir                                # For comparision (before/after)
    

    One could also provide more paths in one go; example: git filter-repo --path "path/to/file1" --path "path/to/file2" --path "dir1"

    Note that you’ll need to specify full paths to the files you want to keep; the ‘filter-branch’ solution used a grep hence the paths weren’t as relevant in that case.

    What this does is goes through all commits in the clone of the original repository looking for files which don’t match the files you want to keep and removes their entries in the index.

    Afterwards you’re left with just the commits for just the files you’re interested in (i.e. “file4.txt” or “path/to/file1”, “path/to/file2” and “dir1”).

    Tipp: Normally, ‘filter-repo’ works by ignoring the filenames specified (they are, as the name suggests, filtered out). But if we want the inverse behavior, we want ‘filter-repo’ to ignore everything except the specified file. For that, pass --invert-paths as an additional argument.

  4. Clean up the clone repo

    To make sure that everything is cleaned up, you can also run Git’s garbage collector explicitly, so that everything that isn’t required really has been purged:

     C:\devel\git\gittestrepo_clone> git gc --aggressive
    
    • Either put the file(s) in a brand new repo…

      { Now rename the directory to something more appropriate for the subproject that has been created and reassign the origin remote pointer (assuming, of course, that the remote bare repository has already been created):

         git remote add origin git@git.server.example.com:new_repo_name.git
      

    TO BE CONTINUED }

    • … or merge the file(s) and history to an existing repo

         C:\devel\git\GitTestRepo2> git remote add templocal "C:\devel\git\gittestrepo_clone"
         C:\devel\git\GitTestRepo2> git fetch templocal
         C:\devel\git\GitTestRepo2> git merge templocal/master --allow-unrelated-histories
         C:\devel\git\GitTestRepo2> git remote rm templocal
         C:\devel\git\GitTestRepo2> git push
      
  5. Remove the moved files from the original repo

    Of course, the moved files need to be removed from the original repository (the real repo, not the clone) and a commit message should indicate where they ended up.

     C:\devel\git\GitTestRepo2>cd ..
     C:\devel\git>cd GitTestRepo
     C:\devel\git\GitTestRepo>git rm file4.txt
     C:\devel\git\GitTestRepo>git commit -a -m "Moved to GitTestRepo2"
     C:\devel\git\GitTestRepo>git push
    

Split a repository in two (or integrate an old repository into a new one), with preserved Git history

This how I brought CLIOptions and InitFile into the newly created (local) repository Magpie (2021-02-11) Based on https://saintgimp.org/2013/01/22/merging-two-git-repositories-into-one-repository-without-losing-file-history/

# Create the new repository
C:> cd C:\devel\git
C:\devel\git> git init Magpie
C:\devel\git> cd Magpie

# Before we do a merge, we have to have an initial commit,
# so we'll make a dummy commit (for that, the repo cannot be completely empty)
dir > deleteme.txt
git add .
git commit -m "Initial dummy commit"

# Add a remote for and fetch the old repo
git remote add -f CLIOptions git@bitbucket.org:<username>/CLIOptions.git

# Merge the files from old_a/master into new/master
git merge CLIOptions/master --allow-unrelated-histories

# Clean up our dummy file because we don't need it any more
git rm .\deleteme.txt
git commit -m "Clean up initial file"

# Move the old_a repo files and folders into a subdirectory,
# so they don't collide with the other repo coming later
mkdir CLIOptions
dir -exclude CLIOptions | %{git mv $_.Name CLIOptions}

# Commit the move
git commit -m "Move 'CLIOptions' files into sub-directory"

# Do the same thing for InitFile
git remote add -f InitFile git@bitbucket.org:<username>/InitFile.git
git merge InitFile/master --allow-unrelated-histories
mkdir InitFile
dir -exclude CLIOptions, InitFile | %{git mv $_.Name InitFile} # Attention: Exclude also all previous folders!
git commit -m "Move 'InitFile' files into sub-directory"

Rename a Git repository (on BitBucket.org)

  1. Logon to BitBucket; go to the repo’s settings and change the name. Then copy the new URL from the web interface’s ‘Clone’ menu, better logout/login again; else it still showed the old URL for me.

  2. Checkout a new clone; or, alternatively: See item 3.

  3. Switch into your local project and point the local repo’s Git configuration to the new URL: (You can compare the before/after setting with git config --list: Look for remote.origin.url=git@bitbucket.org:<username>/<NAME>.git.)

     git remote set-url origin git@bitbucket.org:<username>/<NAME>.git
    
  4. Finally and optionally, rename your local project directory. (Git doesn’t care what name the directory has.)


Migrate a Mercurial project to a Git project (on BitBucket.org)

Bitbucket: Converting Hg repositories to Git

The following expect a Windows system and TortoiseHg to be installed and SSH keys to be configured for BitBucket! And your local Hg repository should be up to date (push/pull/merge before, if needed).

Issues, Wikis and other BitBucket features will not be considered! (Note: You can export issues to a zip file and import that zip file into the new BitBucket repository (overwriting all previous issues there, if any exist already).)

  1. Enable the Hg-Git extension in TortoiseHg

    TortoiseHg -> Global Settings -> Extensions Tab -> [x] hggit -> OK.

    That will also be reflected in the Hg configuration file (C:\Users<username>\mercurial.ini) and look like this:

     [extensions]
     hggit = 
    
  2. Backup BitBucket project

    Backup the soon-to-be-converted Hg repo on BitBucket by example by renaming it in the web interface:

    -> Settings: General: Repository details: Update repository details: Name: “” rename to “-hg”

  3. Create a new Git repo on BitBucket

    Create a new repository on BitBucket with the now free original name “” and select Git as the version control system; don’t create a README file.

  4. Convert

    Go to the local working diretory of your Hg repo and do this (adjust path and URL accordingly):

     C:\path\to\hg\proj> hg bookmark -r default master
     C:\path\to\hg\proj> hg push git+ssh://git@bitbucket.org:<username>/<proj>.git
    

    If that push command results in an error, you might must update your TortoiseHg/Mercurial installation. In my case, I got fatal error, ending with line:

     genpack() got an unexpected keyword argument 'ofs_delta'
    

    That was with TortoiseHg 4.5.3 (x64); after updating to TortoiseHg 4.9.1 (x64), all was fine.

  5. Modify or clone Git repo

     C:\path\to\git\repos> git clone git@bitbucket.org:<username>/<proj>.git
    

See link above for tips on how to modify an existing working directory instead of downloading a new clone.


Enable Bitbucket Pages

From https://www.w3schools.com/git/git_remote_pages.asp?remote=bitbucket

Bitbucket Cloud lets you publish a website directly from your repository.

Enable

  1. Create a new repository named <username>.bitbucket.io.
  2. Add your website files (like index.html) to the repository.
    Bitbucket uses the master branch and the root folder by default for Bitbucket Pages.
    Make sure your site files are in the root of the repository and pushed to the master branch.
  3. Push your changes to the repo on Bitbucket.

The site will be available at https://<username>.bitbucket.io/.

Disable

  1. Go to your repository’s Settings -> Pages.
  2. Click “Disable” or delete the whole repository if you no longer need it.

Push to multiple remote repositories


TODOs

[TODO] Worktree

Statt “Stash”: Stattdessen mehrere Working Copies (in unterschiedlichen Verzeichnissen) auschecken:

git worktree add ../blah master
                 +       +- Target Branch
                 |
                 +- Target Directory (Outside! Nesting beneath the Git repo is not recommended!)

example-proj            <-- The main worktree (with a '.git' directory)
example-proj-hotfix     <-- Another worktree (has only a '.git' file(!) that points to the main worktree)

Getting rid of it again (after commit & push):

git worktree remove <DIR>      --> "." from within; <PATH> from outside.

List:

git worktree list

[TODO] Checkout

git checkout .

Note: Discards all changes that you haven’t commited yet!
Can’t be undone!
But: Staged files [via git add] remain staged!

Discard all changes, incl. already-staged & unstaged (removes on only the mark)

git reset HEAD   |     git reset --hard HEAD
git checkout .

HEAD == “Name for the commit one has currently checked-out”

[TODO] ???

       > git reset HEAD ................................
       :                                               :
       :.......o <- staged changes/uncommited changes..:
               :
       HEAD -> o "Blah"
               |
               o "Do that"
               |
               o "Do this"
               
         Local main branch

[TODO] Clean

Remove all untracked files & folders from the repo (can’t be undone):

git clean -df

[TODO] Misc.

[TODO] The main steps/areas of Git

The four main steps(areas?) to submit additions or changes to an existing repo

  1. Change items in a Working Copy
  2. Prepare the changes (= add to the staging area/the index)
  3. Commit changes to the local repo
  4. Push commited changes from local repo to remote repo

[TODO] Common Git Workflow

  1. git switch -C new-branch
  2. (… make changes; time passes…)
  3. git add . (o.Γ€.)
  4. git commit -m “Message…”
  5. git fetch && git rebase Origin/main
  6. git push
  7. (make pull request)