196

Suppose I have 5 local commits. I want to push only 2 of them to a centralized repo (using an SVN-style workflow). How do I do this?

This did not work:

git checkout HEAD~3  #set head to three commits ago
git push #attempt push from that head

That ends up pushing all 5 local commits.

I suppose I could do git reset to actually undo my commits, followed by git stash and then git push -- but I've already got commit messages written and files organized and I don't want to redo them.

My feeling is that some flag passed to push or reset would work.

If it helps, here's my git config

[ramanujan:~/myrepo/.git]$cat config 
[core]
        repositoryformatversion = 0
        filemode = true
        bare = false
        logallrefupdates = true
[remote "origin"]
        url = ssh://server/git/myrepo.git
        fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
        remote = origin
        merge = refs/heads/master
Dom
  • 1,687
  • 6
  • 27
  • 37
ramanujan
  • 5,581
  • 5
  • 30
  • 31

5 Answers5

238

Assuming your commits are on the master branch and you want to push them to the remote master branch:

$ git push origin master~3:master

If you were using git-svn:

$ git svn dcommit master~3

In the case of git-svn, you could also use HEAD~3, since it is expecting a commit. In the case of straight git, you need to use the branch name because HEAD isn't evaluated properly in the refspec.

You could also take a longer approach of:

$ git checkout -b tocommit HEAD~3
$ git push origin tocommit:master

If you are making a habit of this type of work flow, you should consider doing your work in a separate branch. Then you could do something like:

$ git checkout master
$ git merge working~3
$ git push origin master:master

Note that the "origin master:master" part is probably optional for your setup.

Ryann Graham
  • 8,079
  • 2
  • 29
  • 32
  • 20
    Note: you do not have to use `master~3`. Any reference to the desired "up to" commit is equally valid, such as `HEAD~3` or `HEAD~~~`, or the specific SHA, or a tag which labels that commit. – Kaz Jul 04 '14 at 18:49
  • 2
    Good stuff. A warning though: these examples push to origin master. If you are copying and pasting this solution, you may end up accidentally updating the master branch. (Of course, you should always be careful and double-check your command before issuing a `git push`...) – nofinator Jul 23 '15 at 15:22
  • It appears that this pushes the commit, but doesn't add the branch remotely. – Nateowami Feb 17 '17 at 10:08
  • @Nateowami for that you'll need to specify something other than `master` for the remote side of the refspec, like `git push origin tocommit:newbramch` – Ryann Graham Feb 17 '17 at 16:19
  • I see. The branch name already existed locally; I suppose it didn't like that. The remote didn't have the branch name yet though. – Nateowami Feb 17 '17 at 16:43
41

Short answer:

git push <latest commit SHA1 until you want commits to be pushed>

Examples:

git push origin fc47b2:master

git push origin HEAD~2:main

Long answer:

Commits are linked together as a chain with a parent/child mechanism. Thus, pushing a commit actually also pushes all parent commits to this commit that where not known to the remote. This is implicitly done when you git push the current commit: all the previous commits are also pushed because this command is equivalent to git push HEAD.

So the question might be rewritten into How to push a specific commit and this specific commit might be HEAD~2, for example.

If the commits you want to push are non-consecutive, simply re-order them with a git rebase -i before the specific push.

Tim
  • 2,052
  • 21
  • 30
16

What I do is work on a local branch called "work". This branch contains all the temporary commits (like workarounds or private build options or whatever) that I don't intend to push to the upstream repository. I work away on that branch, then when I want to commit I switch to the master branch, cherry-pick the appropriate commits that I do want to commit, then push master.

After pulling changes from the upstream into my master branch, I git checkout work and git rebase master. That rewrites all my local changes to be at the end of the history.

I'm actually using git svn with this workflow, so my "push" operation involves git svn dcommit. I also use tig which is a nice text mode gui repository viewer, to cherry-pick the appropriate commits to master.

Greg Hewgill
  • 951,095
  • 183
  • 1,149
  • 1,285
  • with git svn dcommit, you can specify a commit to dcommit up to, so the desired effect is quite trivial with git-svn. – Ryann Graham Mar 02 '09 at 23:18
  • 1
    There are disadvantages to this approach (summarized here http://stackoverflow.com/a/881014/1116674). A good alternative is to create branches for every feature you're working on, and a `work` branch. Then, you merge specific branches into `master` so you don't lose the history on them. When working with `work`, you merge all your branches into it. It's more overhead, but might be worth it in some cases. – Hudon May 07 '12 at 15:47
16

By default, git-push pushes all branches. When you do this:

 git checkout HEAD~3  #set head to three commits ago
 git push #attempt push from that head

You move to a detached HEAD (you're not on any branch) and then you push all the branches, including the local master (which is still where it was) to the remote master.

The manual solution is:

 git push origin HEAD:master

If you find the default behaviour of pushing all branches confusing (and dangerous!), add this to your ~/.gitconfig:

 [remote.origin]
    push = HEAD

Then only the branch you're on is pushed. In your example (a detached head), you would have got this error message, rather than accidentally pushing the wrong commits:

 error: unable to push to unqualified destination: HEAD
Thomas Leonard
  • 7,068
  • 2
  • 36
  • 40
10

1) Use "git rebase" to reorder your commits, if you want to.

git rebase -i

This command will display something like this in your editor ( I am using vim )

pick 4791291 commitA
pick a2bdfbd commitB
pick c3d4961 commitC
pick aa1cefc commitD
pick 9781434 commitE

# Rebase ..............
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#  x, exec = run command (the rest of the line) using shell
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out




^G Get Help         ^O WriteOut         ^R Read File        ^Y Prev Page                ^K Cut Text         ^C Cur Pos
^X Exit             ^J Justify          ^W Where Is         ^V Next Page            ^U UnCut Text       ^T To Spell

2) Reorder your your commits according to your choice by simple cut paste. Suppose the new order is

pick 9781434 commitE

pick c3d4961 commitC

pick 4791291 commitA

pick aa1cefc commitD

pick a2bdfbd commitB

Make these changes in your editor and press ctrl+ O (writeOut)

Or you can also use

git rebase -i HEAD~<commitNumber>

You can check the new sequence with

git log

3) Now use

git push <remoteName> <commit SHA>:<remoteBranchName>

If only one branch at remote(origin) and one at local(master), just use

git push <commit SHA>
git push aa1cefc

This will push commitB and commitD.

Yogesh Yadav
  • 4,557
  • 6
  • 34
  • 40
  • Your order is wrong.. As said in description "they are executed from top to bottom." So when you have new order from 2) and you will do "git push " This will actually push commitE, commitC, commitA and commitD otherwise..thank you for help – Pavol Aug 20 '20 at 12:51