Skip to content

Latest commit

 

History

History
924 lines (525 loc) · 14.4 KB

2017-02-02-git-merge.md

File metadata and controls

924 lines (525 loc) · 14.4 KB

template: title

.mega-octicon.octicon-circuit-board[]

Greatest Hits from Ask-Git-Core

Patrick McKenna


template: content

The What .octicon.octicon-circuit-board[]

A selection of:

  • how to's

  • general advice for working with Git


template: content

Workshop mechanics .octicon.octicon-circuit-board[]

Assumptions:

  • You're comfortable with command line Git

  • You understand the basics of Git's internals

  • You're familiar with the basics of shell scripting


template: content

Workshop mechanics .octicon.octicon-circuit-board[]

Assumptions:

  • You're comfortable with command line Git

  • You understand the basics of Git's internals

  • You're familiar with the basics of shell scripting

Note: examples checked on Git 2.11.0


template: section

.mega-octicon.octicon-circuit-board[]

Simplifying common tasks


template: content

Simplifying common tasks .octicon.octicon-circuit-board[]

For example:

What commits were added to <branch> by my last pull?

--

git-show-new <branch>

template: content

List commits added to <branch> by last pull .octicon.octicon-circuit-board[]

First, we need accept a <branch> arg

--

... and provide a sensible default if none supplied

--

#!/bin/bash

if [ $1 ]; then
  branch="$1"
else
  branch="HEAD"
fi
# ...

template: content

List commits added to <branch> by last pull .octicon.octicon-circuit-board[]

Next: generate the list of new commits

--

Use git rev-list

--

It generates and traverses commit graphs


template: content

List commits added to <branch> by last pull .octicon.octicon-circuit-board[]

General syntax for commits reachable from A but not B:

--

git rev-list A ^B

--

(A, B are commit-ish)


template: content

List commits added to <branch> by last pull .octicon.octicon-circuit-board[]

General syntax for commits reachable from A but not B:

git rev-list A ^B

Could use alternate sytax...

git rev-list B..A

template: content

List commits added to <branch> by last pull .octicon.octicon-circuit-board[]

git rev-list A ^B

--

In our case:

  • A is the commit to which <branch> currently points

template: content

List commits added to <branch> by last pull .octicon.octicon-circuit-board[]

git rev-list A ^B

In our case:

  • A is the commit to which <branch> currently points

  • B is the commit <branch> pointed to before our latest pull


template: content

List commits added to <branch> by last pull .octicon.octicon-circuit-board[]

How do we specify the commit <branch> used to point to?

--

Using @{<n>} syntax:

--

<branch>@{<n>}

--

Gets the nth prior value of <branch>


template: content

List commits added to <branch> by last pull .octicon.octicon-circuit-board[]

Hence:

git rev-list <branch> ^<branch>@{1}

template: content

List commits added to <branch> by last pull .octicon.octicon-circuit-board[]

A final version might look like this:

#!/bin/bash

if [ $1 ]; then
  branch="$1"
else
  branch="HEAD"
fi

printf "\n%s%s\n\n" $(git rev-list $branch ^$branch@{1} | wc -l) \
  " commits were added by your last update to $branch:"
git --no-pager log $branch ^$branch@{1} --oneline

template: content

Related advice .octicon.octicon-circuit-board[]

--

Be careful with Git's "convenience" syntax, e.g.@

--

BASE=$(git merge-base @ @{u} 2>/dev/null || echo "none")

--

What are those @ symbols?!

--

Those are the Unicode code points for the "the Git command line was getting too comprehensible" character in Swahili


template: content

Avoid Git syntax shortcuts in scripts .octicon.octicon-circuit-board[]

BASE=$(git merge-base @ @{u} 2>/dev/null || echo "none")

--

  • the @ is short for HEAD

template: content

Avoid Git syntax shortcuts in scripts .octicon.octicon-circuit-board[]

BASE=$(git merge-base @ @{u} 2>/dev/null || echo "none")
  • the @ is short for HEAD

  • @{u} is short for @{upstream}


template: content

Avoid Git syntax shortcuts in scripts .octicon.octicon-circuit-board[]

BASE=$(git merge-base @ @{u} 2>/dev/null || echo "none")
  • the @ is short for HEAD

  • @{u} is short for @{upstream}

Using that in scripting is a terrible idea, IMHO.


template: content

Avoid Git syntax shortcuts in scripts .octicon.octicon-circuit-board[]

Git can be tricky enough as is

--

Make scripts reader-friendly where possible

--

-BASE=$(git merge-base @ @{u} 2>/dev/null || echo "none")
+BASE=$(git merge-base HEAD @{upstream} 2>/dev/null || echo "none")

template: section

.mega-octicon.octicon-circuit-board[]

Integrating with Git


template: content

Integrating with Git .octicon.octicon-circuit-board[]

  • add branch metadata

template: content

Integrating with Git .octicon.octicon-circuit-board[]

  • add branch metadata

  • using exclude patterns in scripts


template: content

Add metadata to a branch .octicon.octicon-circuit-board[]

--

How:

git branch --edit-description <branch>

--

Data written to .git/config

--

  • follows renames
  • deleted when <branch> deleted

template: content

Add metadata to a branch .octicon.octicon-circuit-board[]

.git/config meant to be extensible for third party uses


template: content

Using exclude patterns in scripts .octicon.octicon-circuit-board[]


template: content

Using exclude patterns in scripts .octicon.octicon-circuit-board[]

Ex: testing scripts that respect contents of:

  • .gitignore

  • .git/info/exclude


template: content

Filter based on Git exclude patterns .octicon.octicon-circuit-board[]

Re-implementing Git exclude patterns non-trivial


template: content

Filter based on Git exclude patterns .octicon.octicon-circuit-board[]

Re-implementing Git exclude patterns non-trivial

Essential plumbing command:

git check-ignore <pathname>

--

Returns <pathname> if it matches an exclude pattern


template: content

Filter based on Git exclude patterns .octicon.octicon-circuit-board[]

git check-ignore <pathname>

template: content

Filter based on Git exclude patterns .octicon.octicon-circuit-board[]

git check-ignore <pathname>
  • no recursive option for wildcard searches

--

# assume .gitignore includes the pattern: *sample.py
> git check-ignore plugins/*
# output would include
#   plugins/basic_sample.py
# output would not include
#   plugins/operators/basic_sample.py

template: content

Filter based on Git exclude patterns .octicon.octicon-circuit-board[]

git check-ignore <pathname>
  • no recursive option for wildcard searches

  • can't filter against custom ignore file


template: content

Filter based on Git exclude patterns .octicon.octicon-circuit-board[]

git check-ignore <pathname>
  • no recursive option for wildcard searches

  • can't filter against custom ignore file

  • not listed on git-scm.com/docs (but it's there)


template: content

Filter based on Git exclude patterns .octicon.octicon-circuit-board[]

First and third issues can solved:

--

git ls-files --others <pathname(s)>

--

Shows untracked files


template: content

Filter based on Git exclude patterns .octicon.octicon-circuit-board[]

Ex: pre-commit hook to do Python linting

--

# in .git/hooks/pre-commit
python_dirs= #...
pep8 \
  --exclude $(git ls-files --others $python_dirs | tr '\n', ',') \
  $python_dirs

template: section

.mega-octicon.octicon-circuit-board[]

Moving history between repos


template: content

Moving history - scenario .octicon.octicon-circuit-board[]

2 separate repos, each with history

--

Import <subdir> from <source-repo> into <target-repo>

--

(and preserve history of <subdir>)


template: content

Moving history - scenario .octicon.octicon-circuit-board[]

# before
.<source-repo>              .<target-repo>      
|-- src                     |-- src
|-- bin                     |-- bin
|-- <subdir>

# after
.<source-repo>              .<target-repo>      
|-- src                     |-- src
|-- bin                     |-- bin
                            |-- <subdir>

template: content

Moving history - scenario .octicon.octicon-circuit-board[]

  • a one-time operation -> submodules inappropriate

template: content

Moving history - scenario .octicon.octicon-circuit-board[]

  • a one-time operation, so submodules inappropriate

    • e.g. split apart a large project

template: content

Moving history - scenario .octicon.octicon-circuit-board[]

  • a one-time operation -> submodules inappropriate

    • e.g. split apart large project
  • assume <subdir> does not exist in <target-repo>, and that we want to use the same name


template: content

Moving history .octicon.octicon-circuit-board[]

Step 1: construct a "synthetic" history

--

(all commits in <source-repo> that touch files in <subdir>)

--

In <source-repo>:

git subtree split --branch <branch> --prefix <subdir>

template: content

Moving history .octicon.octicon-circuit-board[]

git subtree split --branch <branch> --prefix <subdir>

template: content

Moving history .octicon.octicon-circuit-board[]

git subtree split --branch <branch> --prefix <subdir>
  • creates a new branch

template: content

Moving history .octicon.octicon-circuit-board[]

git subtree split --branch <branch> --prefix <subdir>
  • creates a new branch
  • synthesizes commit history

template: content

Moving history .octicon.octicon-circuit-board[]

git subtree split --branch <branch> --prefix <subdir>
  • creates a new branch
  • synthesizes commit history
    • each commit has contents of <subdir> at root of project
    • repeated splits produce identical histories

template: content

Moving history .octicon.octicon-circuit-board[]

Step 2: get contents of <subdir> into <target-repo>:

--

In <target-repo>:

git subtree add --prefix <subdir> <source-repo> <branch>

template: content

Moving history .octicon.octicon-circuit-board[]

git subtree add --prefix <subdir> <source-repo> <branch>

--

  • imports synthetic history, puts contents into <subdir>

template: content

Moving history - caveats .octicon.octicon-circuit-board[]

Done...with some caveats

--

git log may not work as you expect


template: content

Moving history - caveats .octicon.octicon-circuit-board[]

Look at the history of a given<file> in <subdir>:

--

In <target-repo>:

>>> git log -- <subdir>/<file>
commit 2e259f35fea13ca6bbfb8bd33be6476caea5cc86
Merge: e6a4851 d0e7410

    Add '<subdir>' from commit 'd0e74107e4517e48888b3de5bd7b129bc12a041f'
    
    git-subtree-dir: <subdir>
    git-subtree-mainline: e6a4851ca13c9fa76fd25277b1d1bb6cd7e61519
    git-subtree-split: d0e74107e4517e48888b3de5bd7b129bc12a041f
>>>

--

Only 1 commit—didn't we import the full history?


template: content

Moving history - caveats .octicon.octicon-circuit-board[]

Adding --follow doesn't help

--

There's a workaround:

--

git log -- *<file>

--

But limitations to this


template: content

Moving history - v2 .octicon.octicon-circuit-board[]

In <target-repo>:

--

git checkout -b <branch>

template: content

Moving history - v2 .octicon.octicon-circuit-board[]

In <target-repo>:

git checkout -b <branch>
git remote add  -f <remote-name> <source-repo>

template: content

Moving history - v2 .octicon.octicon-circuit-board[]

In <target-repo>:

git checkout -b <branch>
git remote add  -f <remote-name> <source-repo>
git merge --allow-unrelated-histories <remote-name>/<branch>

--

git log -- <pathname> should now work as expected


template: content

Moving history - general notes .octicon.octicon-circuit-board[]

filter-branch generalizes better (to processing all branches)...

--

... but it is notoriously tricky to use

--

subtree simpler...

--

... but it lives in /contrib (while filter-branch ships with Git)


template: content

Related history manipulation tools .octicon.octicon-circuit-board[]


template: content

Related history manipulation tools .octicon.octicon-circuit-board[]

  • git checkout --orphan to create new branch w/o parents, disconnected from all history

template: content

Related history manipulation tools .octicon.octicon-circuit-board[]

  • git checkout --orphan to create new branch w/o parents, disconnected from all history

  • for an abbreviated history, try filter-branch with grafts


template: content

Final tidbits .octicon.octicon-circuit-board[]

  • Git doesn't have any notion of "this commit was made on this branch"

template: content

Final tidbits .octicon.octicon-circuit-board[]

  • Git doesn't have any notion of "this commit was made on this branch"
  • Git won't expand built-in commands used as aliases
    • was debated, a case of Git not wanting to let people hurt themselves(!)

template: content

Final tidbits .octicon.octicon-circuit-board[]

  • Git doesn't have any notion of "this commit was made on this branch"
  • Git won't expand built-in commands used as aliases
    • was debated, a case of Git not wanting to let people hurt themselves(!)
  • consider git-sh-setup.sh "scriplet"

template: content

Final tidbits .octicon.octicon-circuit-board[]

  • Git doesn't have any notion of "this commit was made on this branch"
  • Git won't expand built-in commands used as aliases
    • was debated, a case of Git not wanting to let people hurt themselves(!)
  • consider git-sh-setup.sh "scriplet"
  • contribute to Git: submitgit.herokuapp.com

template: content

Final tidbits .octicon.octicon-circuit-board[]

  • Git doesn't have any notion of "this commit was made on this branch"
  • Git won't expand built-in commands used as aliases
    • was debated, a case of Git not wanting to let people hurt themselves(!)
  • consider git-sh-setup.sh "scriplet"
  • contribute to Git: submitgit.herokuapp.com
  • reduce merge conflict pain: github.com/mhagger/git-imerge

template: title

.mega-octicon.octicon-circuit-board[]

Thank You!

Patrick McKenna

[email protected]