Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Git & GitHub  Module 1 – Getting started with Git

Install Git and set up your identity

Install Git on Windows, macOS or Linux, set your name, email and first branch at the right level, and find which settings file each value comes from.

  • Beginner
  • 25 minutes
  • Examples run with git 2.54.0, Python 3.14.8 and Pyodide 314.0.7
  • By MySmartCoPilot
Free lesson, Pro answers: 25 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Install a current Git on Windows, macOS or Linux and check its version
  • Set user.name, user.email and init.defaultBranch at the right configuration level
  • Find where each setting comes from with git config list --show-origin --show-scope
  • Use includeIf to switch identity between personal and college or work repositories
  • Add a few safe aliases and choose an editor for commit messages

Before you start

What you’ll learn · Before you start

What you will learn

  • Install a current Git on Windows, macOS or Linux and check its version
  • Set user.name, user.email and init.defaultBranch at the right configuration level
  • Find where each setting comes from with git config list --show-origin --show-scope
  • Use includeIf to switch identity between personal and college or work repositories
  • Add a few safe aliases and choose an editor for commit messages

Before you start

On this page

You install Git once, and then you tell it who you are. Git writes a name and an email address into every commit you make, so before the first commit you set both, together with the name of the first branch of every new repository: git config set --global user.name "Your Name", git config set --global user.email you@example.com and git config set --global init.defaultBranch main. This lesson installs a current Git on Windows, macOS or Linux, checks which version you have, makes those three settings at the right level and shows how to see which file any setting came from. It also sets up a different email for college or work repositories without having to remember, and a few safe extras: a short alias, the editor for commit messages, and built-in help.

Install Git

The Git project’s download pages point to the usual way of getting Git on each system. Any recent version is fine for this track: the newest release is Git 2.56.0, and the examples here run on Git 2.54.0.

Windows

Install Git for Windows, either with the installer linked from git-scm.com or with winget install --id Git.Git -e --source winget in PowerShell. It comes with Git Bash, a terminal that works the way Git does on macOS and Linux, and the commands in this track work there unchanged. The installer asks a few questions that matter later:

  • The default editor, used when Git needs you to write a commit message. It suggests Vim; if you have never used Vim, pick Notepad, Notepad++ or Visual Studio Code from the list instead.
  • The name of the initial branch in new repositories: choose to override it and keep main.
  • Line endings: keep the suggested choice for now. A later lesson of this track explains why a .gitattributes file in each repository is the better place to settle them.

macOS

Apple ships Git with the Xcode Command Line Tools: run xcode-select --install in Terminal to get them. Like every build that does not come from the Git project itself, Apple’s can lag behind the newest release, and its version line says whose build it is: the Mac that runs this track’s examples prints git version 2.54.0 (Apple Git-157), two releases behind. For a newer Git, use a package manager such as Homebrew, brew install git, or MacPorts, sudo port install git.

Linux

Use your distribution’s package manager: sudo apt-get install git on Debian and Ubuntu, sudo dnf install git on Fedora, sudo pacman -S git on Arch. Distributions package the newest Git at their own pace, and some, such as Red Hat Enterprise Linux, ship older versions; on Ubuntu, the Git project’s page names a package archive, ppa:git-core/ppa, with the latest stable release.

Check the version, then name your first branch

Open a new terminal after installing and run git --version. Then make a repository before changing any setting, to see what a new installation does:

A first repository before and after setting init.defaultBranch Python · fresh_start.py
from gitsession import session

session(r"""
git --version
# A new installation: no settings of your own yet
git init first-try
cd first-try
git branch --show-current
cd ..
# Name the first branch of every new repository main
git config set --global init.defaultBranch main
git init second-try
cd second-try
git branch --show-current
git config get --show-scope --show-origin init.defaultBranch
""")

Output

$ git --version
git version 2.54.0 (Apple Git-157)
# A new installation: no settings of your own yet
$ git init first-try
hint: Using 'master' as the name for the initial branch. This default branch name
hint: will change to "main" in Git 3.0. To configure the initial branch name
hint: to use in all of your new repositories, which will suppress this warning,
hint: call:
hint:
hint: 	git config --global init.defaultBranch <name>
hint:
hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and
hint: 'development'. The just-created branch can be renamed via this command:
hint:
hint: 	git branch -m <name>
hint:
hint: Disable this message with "git config set advice.defaultBranchName false"
Initialized empty Git repository in ~/first-try/.git/
$ cd first-try
$ git branch --show-current
master
$ cd ..
# Name the first branch of every new repository main
$ git config set --global init.defaultBranch main
$ git init second-try
Initialized empty Git repository in ~/second-try/.git/
$ cd second-try
$ git branch --show-current
main
$ git config get --show-scope --show-origin init.defaultBranch
global	file:~/.gitconfig	main

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 fresh_start.py

With no settings at all, Git still gives the first branch its old default name and prints a long hint about it; the Git project plans to change that default to main in Git 3.0. One setting, init.defaultBranch, removes both the old name and the hint for every repository you create from now on. It changes nothing in repositories that already exist. git config get --show-scope --show-origin then says where the value came from: the global scope, the file ~/.gitconfig in your home folder. Some installers set this for you, which is why the hint never appears for some people: the same git config get command shows which file set it.

Tell Git who you are

user.name is your personal name, the way people refer to you, such as “Asha Rao”. It is not a login and has no effect on signing in to any service. user.email is the address that goes into every commit, and anyone who can see the repository can read it, so for public repositories use an address you are happy to publish. GitHub offers each account a no-reply address for exactly this purpose; commits already made keep the address they were made with.

If you skip these settings, Git guesses. It falls back to the EMAIL environment variable and then to your account name and the computer’s network name. Depending on the computer, it then either stops with “unable to auto-detect email address” or commits with an address made from those names, such as asha@asha-laptop.lan, which nobody can reply to. The setting user.useConfigOnly turns the guess off: with it, Git refuses to commit until both values are set, which is what this session shows first:

Identity, then the same key at two levels Python · identity.py
from gitsession import session

session(r"""
git config set --global init.defaultBranch main
# Never guess a name or an email: refuse to commit until both are set
git config set --global user.useConfigOnly true
git init -q notes
cd notes
printf 'Buy chart paper\n' > todo.txt
git add todo.txt
! git commit -q -m "Start a to-do list"
git config set --global user.name "Asha Rao"
git config set --global user.email "asha.rao@example.com"
git commit -q -m "Start a to-do list"
git log --format='%h %an <%ae>: %s'
# The same key again, for this repository only
git config set user.email "asha@college.example.edu"
git config list --show-scope --show-origin
git config get --show-scope --show-origin user.email
""")

Output

$ git config set --global init.defaultBranch main
# Never guess a name or an email: refuse to commit until both are set
$ git config set --global user.useConfigOnly true
$ git init -q notes
$ cd notes
$ printf 'Buy chart paper\n' > todo.txt
$ git add todo.txt
$ git commit -q -m "Start a to-do list"
Author identity unknown

*** Please tell me who you are.

Run

  git config --global user.email "you@example.com"
  git config --global user.name "Your Name"

to set your account's default identity.
Omit --global to set the identity only in this repository.

fatal: no email was given and auto-detection is disabled
[exit status 128]
$ git config set --global user.name "Asha Rao"
$ git config set --global user.email "asha.rao@example.com"
$ git commit -q -m "Start a to-do list"
$ git log --format='%h %an <%ae>: %s'
8b7f734 Asha Rao <asha.rao@example.com>: Start a to-do list
# The same key again, for this repository only
$ git config set user.email "asha@college.example.edu"
$ git config list --show-scope --show-origin
global	file:~/.gitconfig	init.defaultbranch=main
global	file:~/.gitconfig	user.useconfigonly=true
global	file:~/.gitconfig	user.name=Asha Rao
global	file:~/.gitconfig	user.email=asha.rao@example.com
local	file:.git/config	core.repositoryformatversion=0
local	file:.git/config	core.filemode=true
local	file:.git/config	core.bare=false
local	file:.git/config	core.logallrefupdates=true
local	file:.git/config	core.ignorecase=true
local	file:.git/config	core.precomposeunicode=true
local	file:.git/config	user.email=asha@college.example.edu
$ git config get --show-scope --show-origin user.email
local	file:.git/config	asha@college.example.edu

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 identity.py

The refused commit asks for user.email and user.name, and once both are set, git log shows them in the commit. The last part sets user.email a second time without --global, so it goes into this repository’s own file, .git/config.

git config set is the current spelling: Git 2.46 replaced the old mode options with subcommands such as list, get and set. The older form, git config --global user.name "Asha Rao", still works, and you will see it in many guides and in Git’s own messages, such as the one in the refused commit.

Where settings live, and which one wins

Git reads its settings from several files, one after another, and for a setting with one value the last value it finds wins. Each level therefore overrides the ones before it:

Five levels of Git settings, read in order: system, global, local, worktree, command line; each later level overrides the earlier.System: /etc/gitconfigevery user of the computergit config set --systemGlobal: ~/.gitconfigyou, in every repositorygit config set --globalLocal: .git/configthis repository onlygit config setWorktree: .git/config.worktreeone working tree, if turned ongit config set --worktreeCommand line: git -c name=valueone command onlyoverridden byoverridden byoverridden byoverridden by

The levels of Git settings, from the weakest to the strongest

Text description of the diagram

The diagram is a column of five levels. Git reads them from the top down, and for a setting that has one value, the last value it finds wins.

  1. System: the file /etc/gitconfig (its exact place depends on how Git was installed), for every user of the computer, written with git config set --system.
  2. Global: ~/.gitconfig in your home folder (or ~/.config/git/config), for you in every repository, written with git config set --global.
  3. Local: .git/config inside one repository, written with git config set and no level option.
  4. Worktree: .git/config.worktree, for one working tree of a repository, used only when the repository has turned this file on.
  5. Command line: git -c name=value before a command, for that one command.

Each level overrides the levels above it, so a local user.email beats the global one inside that repository.

The three levels you will use most:

  • System, the file /etc/gitconfig (its place depends on how Git was installed): settings for every user of the computer, written with git config set --system, which needs administrator rights. It is for settings an administrator wants everyone to have.
  • Global, the file ~/.gitconfig (or ~/.config/git/config): your settings, for every repository you use, written with git config set --global. Your name, email, editor, aliases and default branch belong here.
  • Local, the file .git/config inside a repository: settings for that repository only, written with git config set and no level option. It is the place for a different email or setting in one project.

Two more levels exist. A worktree file, .git/config.worktree, applies to one working tree of a repository and is read only when the repository has turned it on. And git -c name=value sets a value for one command, above all files. The --show-scope --show-origin list in the session above shows the levels at work. The global lines come from ~/.gitconfig, the local lines from .git/config. The core.* lines were written by git init itself, and two of them appear only on some systems: git init sets core.ignorecase where the disk does not tell upper from lower case in file names, as on a Mac, and core.precomposeunicode is used only by Git on macOS, for how the system spells accented file names. user.email is set at both levels, and inside this repository the local one wins, as git config get shows.

One email for college, another for everything else

Changing user.email in every new repository by hand is easy to forget. A conditional include does it by folder: an includeIf section in your global file names another settings file to read, but only for repositories inside a given folder.

A college email for every repository under ~/college/ Python · two_emails.py
from gitsession import session

session(r"""
git config set --global init.defaultBranch main
git config set --global user.name "Asha Rao"
git config set --global user.email "asha.rao@example.com"
# Repositories anywhere under ~/college/ also read ~/.gitconfig-college
git config set --global includeIf.gitdir:~/college/.path "~/.gitconfig-college"
git config set --file ~/.gitconfig-college user.email "asha@college.example.edu"
cat ~/.gitconfig
cat ~/.gitconfig-college
git init -q college/physics-lab
git init -q personal/blog
cd college/physics-lab
git config get --show-origin user.email
cd ../../personal/blog
git config get --show-origin user.email
""")

Output

$ git config set --global init.defaultBranch main
$ git config set --global user.name "Asha Rao"
$ git config set --global user.email "asha.rao@example.com"
# Repositories anywhere under ~/college/ also read ~/.gitconfig-college
$ git config set --global includeIf.gitdir:~/college/.path "~/.gitconfig-college"
$ git config set --file ~/.gitconfig-college user.email "asha@college.example.edu"
$ cat ~/.gitconfig
[init]
	defaultBranch = main
[user]
	name = Asha Rao
	email = asha.rao@example.com
[includeIf "gitdir:~/college/"]
	path = ~/.gitconfig-college
$ cat ~/.gitconfig-college
[user]
	email = asha@college.example.edu
$ git init -q college/physics-lab
$ git init -q personal/blog
$ cd college/physics-lab
$ git config get --show-origin user.email
file:~/.gitconfig-college	asha@college.example.edu
$ cd ../../personal/blog
$ git config get --show-origin user.email
file:~/.gitconfig	asha.rao@example.com

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 two_emails.py

gitdir:~/college/ matches every repository whose .git folder is anywhere under ~/college/: the ~ stands for your home folder, and a pattern that ends with / matches everything inside that folder, however deep. Inside college/physics-lab, Git also reads ~/.gitconfig-college, and its email, read later, wins; in personal/blog, the global email applies. Since the include sits in your global file, it works for every repository you clone or create under that folder from now on. If you prefer to be asked instead, set only user.name globally, add user.useConfigOnly true, and Git refuses to commit in each new repository until you set its email.

New in Git 2.56

Git 2.56 can also match on the location of the working tree, with an includeIf "worktree:…" condition that takes the same kind of pattern. Git 2.54, which runs this track’s examples, does not have it.

Aliases, the editor and help

An alias, an editor and short help Python · extras.py
from gitsession import session

session(r"""
git config set --global init.defaultBranch main
git config set --global user.name "Asha Rao"
git config set --global user.email "asha.rao@example.com"
git init -q notes
cd notes
printf 'Buy chart paper\n' > todo.txt
git add todo.txt
git commit -q -m "Start a to-do list"
printf 'Book the physics lab\n' >> todo.txt
git commit -q -am "Add the lab booking"
# An alias: git lg runs a long log command
git config set --global alias.lg "log --oneline --graph --decorate --all"
git lg
# An alias cannot replace a command Git already has
git config set --global alias.status "log --oneline"
git status
# The editor for commit messages, and the one Git will really use
git config set --global core.editor "code --wait"
git var GIT_EDITOR
# Short help for one command
! git init -h
""")

Output

$ git config set --global init.defaultBranch main
$ git config set --global user.name "Asha Rao"
$ git config set --global user.email "asha.rao@example.com"
$ git init -q notes
$ cd notes
$ printf 'Buy chart paper\n' > todo.txt
$ git add todo.txt
$ git commit -q -m "Start a to-do list"
$ printf 'Book the physics lab\n' >> todo.txt
$ git commit -q -am "Add the lab booking"
# An alias: git lg runs a long log command
$ git config set --global alias.lg "log --oneline --graph --decorate --all"
$ git lg
* 9dec939 (HEAD -> main) Add the lab booking
* 8b7f734 Start a to-do list
# An alias cannot replace a command Git already has
$ git config set --global alias.status "log --oneline"
$ git status
On branch main
nothing to commit, working tree clean
# The editor for commit messages, and the one Git will really use
$ git config set --global core.editor "code --wait"
$ git var GIT_EDITOR
code --wait
# Short help for one command
$ git init -h
usage: git init [-q | --quiet] [--bare] [--template=<template-directory>]
                [--separate-git-dir <git-dir>] [--object-format=<format>]
                [--ref-format=<format>]
                [-b <branch-name> | --initial-branch=<branch-name>]
                [--shared[=<permissions>]] [<directory>]

    --[no-]template <template-directory>
                          directory from which templates will be used
    --[no-]bare           create a bare repository
    --shared[=<permissions>]
                          specify that the git repository is to be shared amongst several users
    -q, --[no-]quiet      be quiet
    --[no-]separate-git-dir <gitdir>
                          separate git dir from working tree
    -b, --[no-]initial-branch <name>
                          override the name of the initial branch
    --[no-]object-format <hash>
                          specify the hash algorithm to use
    --[no-]ref-format <format>
                          specify the reference format to use

[exit status 129]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 extras.py

  • Aliases shorten long commands: alias.lg makes git lg print the history as a graph with branch names. An alias cannot replace a command Git already has, so the status alias in the session is ignored and git status still runs the real command. The manual explains why: hiding existing commands would confuse scripts.
  • The editor opens when Git needs you to write a message, for example a commit without -m. core.editor "code --wait" picks Visual Studio Code; --wait makes Git wait until you close the file. Other common choices are nano and vim. git var GIT_EDITOR prints the editor Git will actually use.
  • Help: git help config opens the full manual of a command, and git config -h prints its short usage. In Git 2.54, as the session shows, -h ends with exit status 129, which a script sees as a failure. Git 2.56 changed most commands to exit with status 0 when you ask for help directly.

Check your settings

Run git config list --show-scope --show-origin in any repository and read it from the bottom up: the last line for a name is the value Git uses. The exercise below asks you to write that rule as a small Python function and test it with the sample tests.

Exercise · Easy · Python

Find the value Git uses for a setting

git config list --show-scope prints every setting Git read, in the order it read them, one per line: the scope, a tab, then name=value. For example:

global	user.email=asha.rao@example.com
global	alias.lg=log --oneline --graph
local	user.email=asha@college.example.edu

Write winning_value(listing, name), which takes that output as one string and the name of a setting, and returns a tuple (value, scope) with the value Git uses and the scope it came from, or None when the setting is not there. For the listing above, winning_value(listing, "user.email") returns ("asha@college.example.edu", "local").

Follow Git's rules:

  • When a name appears more than once, the last line wins.
  • The first part of a name (the section, such as user) and the last part (the variable, such as email) are compared without regard to case, so User.Email finds user.email. A middle part, such as Main in branch.Main.remote, must match exactly.
  • A value can contain = itself (alias.lg=log --format=%h): only the first = of a line separates the name from the value.
  • Skip empty lines.

Starter code · winner.py

def winning_value(listing, name):
    """Return (value, scope) for the setting `name` in the output of git config list --show-scope, or None."""
    return None
The sample tests · test_winner.py
from winner import winning_value


def test_later_level_wins():
    """a local value beats the global one"""
    listing = "global\tuser.email=asha.rao@example.com\nlocal\tuser.email=asha@college.example.edu\n"
    assert winning_value(listing, "user.email") == ("asha@college.example.edu", "local")


def test_not_set():
    """returns None for a setting that is not there"""
    listing = "global\tuser.name=Asha Rao\n"
    assert winning_value(listing, "user.email") is None


def test_names_ignore_case():
    """section and variable names are compared without case"""
    listing = "global\tinit.defaultbranch=main\n"
    assert winning_value(listing, "init.defaultBranch") == ("main", "global")
    assert winning_value(listing, "INIT.DEFAULTBRANCH") == ("main", "global")


def test_value_with_equals():
    """only the first = separates the name from the value"""
    listing = "global\talias.lg=log --format=%h %s\n"
    assert winning_value(listing, "alias.lg") == ("log --format=%h %s", "global")


def test_middle_part_is_exact():
    """a subsection must match exactly"""
    listing = "local\tbranch.Main.remote=origin\n"
    assert winning_value(listing, "BRANCH.Main.REMOTE") == ("origin", "local")
    assert winning_value(listing, "branch.main.remote") is None


def test_same_file_twice():
    """the last line wins, even within one file"""
    listing = "global\tcore.editor=nano\n\nglobal\tcore.editor=code --wait\n"
    assert winning_value(listing, "core.editor") == ("code --wait", "global")
A hint

Go through the lines in order and remember the last one whose name matches; there is no need to sort anything. line.partition("\t") splits a line once, at the tab, and setting.partition("=") splits once, at the first =. To compare names, rewrite both in one standard form: the section and the variable in lower case, a middle part as it is. name.partition(".") gives the section, and rest.rpartition(".") the middle part and the variable.

The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Check yourself

6 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 6 Your ~/.gitconfig sets user.email to asha.rao@example.com, and the .git/config of one repository sets it to asha@college.example.edu. Which address does a commit in that repository get?

    Choose one answer.

    Show the answer to question 1

    Answer: asha@college.example.edu, because the local file is read after the global one

    Git reads the system, global, local and worktree files in that order, and for a setting with one value the last value found wins. Inside the repository the local value is read last; in other repositories the local file is not read, so the global one applies.

  2. Question 2 of 6 Where does git config set --global init.defaultBranch main write the setting?

    Choose one answer.

    Show the answer to question 2

    Answer: In the global file in your home folder, ~/.gitconfig (or ~/.config/git/config if only that one exists)

    --global writes your user-wide file, which every repository you use reads. Without a level option, git config set writes the current repository's .git/config, and --system writes the computer-wide file.

  3. Question 3 of 6 You set init.defaultBranch to main today. What happens to a repository you created last month, whose branch has the old default name?

    Choose one answer.

    Show the answer to question 3

    Answer: Nothing; the setting only names the first branch of repositories created from now on

    init.defaultBranch is read when git init creates a repository. Existing repositories keep their branch names; git branch -m renames a branch if you want to change one.

  4. Question 4 of 6 Your global file has an includeIf section for "gitdir:~/college/". Which repository reads the included file?

    Choose one answer.

    Show the answer to question 4

    Answer: ~/college/sem3/physics-lab, because a pattern ending in / matches everything inside the folder

    gitdir: compares the location of the repository's .git folder with the pattern. A leading ~/ is your home folder, and a trailing / makes the pattern match the folder and everything inside it, however deep. A path elsewhere that merely contains the word does not match.

  5. Question 5 of 6 You run git config set --global alias.log "log --oneline". What does git log do afterwards?

    Choose one answer.

    Show the answer to question 5

    Answer: The usual git log; aliases that hide an existing command are ignored

    The git-config manual says that, to avoid confusion and trouble in scripts, aliases that hide existing Git commands are ignored, except for deprecated commands. Pick a new name, such as alias.lg.

  6. Question 6 of 6 Which of these are true about the email address you set in user.email?

    Choose every answer that is right.

    Show the answer to question 6

    Answer:

    • It is recorded in every commit you make
    • Anyone who can read the repository's history can see it

    user.email goes into the author and committer fields of each new commit, and git log shows it to anyone who has the repository. Commits made before a change keep the old address, and the setting has nothing to do with signing in: that is the job of credentials or SSH keys.

Run the sessions yourself

The sessions above use the same helper as the first lesson of this track, gitsession.py. It gives every session a new, empty home folder, so the git config set --global lines there change only that folder, never your own ~/.gitconfig. To replay one on macOS or Linux, keep gitsession.py beside the session file and run, for example, python3 identity.py.

gitsession.py: the helper the sessions use Python · gitsession.py
"""Plays a terminal session with real Git: each command is printed after "$ ", then everything it printed.

The session runs in a new, empty folder called learner/, which is also its home folder, so your own Git settings
change nothing, and GIT_CONFIG_NOSYSTEM=1 makes Git skip the computer's system-wide settings file too. Git never
looks for a repository above that folder, so a session cannot touch a repository you run it from. In the output,
"~" stands for that folder. Commit times are fixed, because a commit's ID covers the time it was made: with the
same files, message, name and time, every run makes the same commit IDs.

Each line of a script is one /bin/sh command (macOS and Linux), except "cd", which the session does itself. A line
that starts with "# " is printed as a comment. A command written as "! command" is meant to fail: its exit status
is printed and the session goes on. Any other command that fails stops the session with exit status 1.
"""

import os
import shutil
import subprocess
import sys
from pathlib import Path

HOME = Path("learner").resolve()
# One fixed moment in Git's own date format: seconds since 1970, then the time zone (India, UTC+05:30).
FIXED_TIME = "@1767240000 +0530"


def _environment():
    # Git's own folder comes first, so every command runs that Git directly (on macOS, /usr/bin/git is a small
    # launcher that looks Git up again on every call).
    git_folder = subprocess.run(["git", "--exec-path"], capture_output=True, text=True, check=True).stdout.strip()
    env = {
        "PATH": os.pathsep.join([git_folder, os.environ.get("PATH", "/usr/bin:/bin")]),
        "HOME": str(HOME),
        "XDG_CONFIG_HOME": str(HOME / ".config"),
        "GIT_CONFIG_NOSYSTEM": "1",
        "GIT_CEILING_DIRECTORIES": str(HOME.parent),
        "GIT_AUTHOR_DATE": FIXED_TIME,
        "GIT_COMMITTER_DATE": FIXED_TIME,
        "LANG": "C",
        "LC_ALL": "C",
        "TERM": "dumb",
    }
    if "TMPDIR" in os.environ:
        env["TMPDIR"] = os.environ["TMPDIR"]
    return env


def _with_tilde(text):
    """Writes the home folder as "~", however the operating system spells its path."""
    for spelling in sorted({str(HOME), os.path.realpath(HOME)}, key=len, reverse=True):
        text = text.replace(spelling, "~")
    return text


def session(script):
    """Runs the script's commands one by one, starting in an empty home folder."""
    shutil.rmtree(HOME, ignore_errors=True)
    HOME.mkdir()
    env = _environment()
    folder = HOME
    for line in script.strip().splitlines():
        command = line.strip()
        if not command:
            continue
        if command.startswith("# "):
            print(command)
            continue
        meant_to_fail = command.startswith("! ")
        if meant_to_fail:
            command = command[2:]
        print("$", command)
        if command.startswith("cd "):
            folder = (folder / command[3:].strip()).resolve()
            continue
        done = subprocess.run(command, shell=True, cwd=folder, env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
        print(_with_tilde(done.stdout), end="")
        if done.returncode != 0:
            print(f"[exit status {done.returncode}]")
            if not meant_to_fail:
                sys.exit(1)
        elif meant_to_fail:
            sys.exit(f"{command} was meant to fail, but it worked")

Interview questions

When the same setting is in your global file and in a repository’s local file, which one does Git use?

Inside that repository, the local one. Git reads its settings files in a fixed order, system first, then global, then local, then the worktree file when it is turned on, and for a setting with one value the last value it finds wins, so each level overrides the ones before it. A git -c name=value option on the command line beats them all, for that one command. Outside the repository, the local file is not read, so the global value applies. To check instead of reasoning, run git config get --show-scope --show-origin user.email in the repository: it prints the winning value with the scope and the file it came from. git config list --show-scope --show-origin prints every value Git read, in order, so the last line for a name is the one in use.

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Fresher to mid

    You commit to college projects and to personal open-source projects from one laptop, and your college asks you to use its address for its repositories. Set up Git so that every repository gets the right email without you having to remember, and explain how you would prove that it works.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Fresher to mid

    A new teammate's first five commits, already pushed, show the author learner with an address ending in .local, and the hosting service links them to no account. Explain what happened, how to fix future commits, and what you would do about the five commits that are already pushed.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • Install Git for Windows, Apple’s command line tools or a package manager, then check with git --version.
  • Set user.name, user.email and init.defaultBranch main once with git config set --global; the email is visible to anyone who can read your commits.
  • Settings are read system, global, local, worktree, then git -c; the last value found wins.
  • git config list --show-scope --show-origin and git config get --show-origin say where each value comes from.
  • includeIf "gitdir:~/college/" switches your email for every repository in a folder; user.useConfigOnly makes Git ask instead of guessing.
  • Aliases cannot replace built-in commands; core.editor sets the message editor and git var GIT_EDITOR shows it.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.