Dado um branch, eu gostaria de ver uma lista de commits que existem apenas naquele branch. Em esta questão vamos discutir maneiras de ver quais commits estão em um ramo, mas não um ou mais especificado outros ramos.
Isso é um pouco diferente. Eu gostaria de ver quais commits estão em um branch, mas não em qualquer outro branch .
O caso de uso está em uma estratégia de ramificação em que algumas ramificações devem apenas ser mescladas e nunca comprometidas diretamente. Isso seria usado para verificar se algum commit foi feito diretamente em um branch "merge-only".
EDIT: Abaixo estão as etapas para configurar um repositório git fictício para testar:
git init
echo foo1 >> foo.txt
git add foo.txt
git commit -am "initial valid commit"
git checkout -b merge-only
echo bar >> bar.txt
git add bar.txt
git commit -am "bad commit directly on merge-only"
git checkout master
echo foo2 >> foo.txt
git commit -am "2nd valid commit on master"
git checkout merge-only
git merge master
Apenas o commit com a mensagem "mau commit diretamente no merge-only", que foi feito diretamente no ramo merge-only, deve aparecer.
git log ^branch1 ^branch2 merge-only-branch
sintaxe?git log ^branch1 ^branch2 merge-only-branch
requer a listagem de todos os ramos. Isso pode ser evitado com o uso inteligente de bash / grep (veja minha resposta abaixo), mas espero que o git tenha algum suporte embutido para isso. Você está correto ao afirmar que todos os branches de mesclagem são remotos (apenas locais são tão bons quanto inexistentes para outros desenvolvedores). O uso--no-merges
omite quaisquer commits que foram mesclados e tiveram seu branch original merge-from excluído, então assume que os branches merge-from são mantidos até que tenham sido fundidos em um branch não-merge-only (ie master).Respostas:
Acabamos de encontrar esta solução elegante
Em seu exemplo, é claro, o commit inicial ainda aparece.
esta resposta não responde exatamente à pergunta, porque o commit inicial ainda aparece. Por outro lado, muitas pessoas que vêm aqui parecem encontrar a resposta que procuram.
fonte
initial valid commit
que é parte de ambos osmerge-only
emaster
ramos. No entanto, se alguém fizer o esforço de colocar o nome do branch atual no final seguido por ^ -nomes de branch com prefixo dos quais o branch atual se originou, isso resolverá metade dos problemas (excluindo coisas que foram mescladas). Ex:git log --first-parent --no-merges merge-only ^master
git log --first-parent --no-merges | grep <branch_name>
Cortesia do meu querido amigo Redmumba :
... onde
origin/merge-only
está o nome do seu branch apenas de mesclagem remota. Se estiver trabalhando em um repositório git somente local, substituarefs/remotes/origin
porrefs/heads
e substitua o nome do branch remoto pelo nome doorigin/merge-only
branch localmerge-only
, ou seja:fonte
git for-each-ref
para listar cada nome de referência na origem egrep -v
para omitir o branch somente de mesclagem.git log
pega uma--not
opção, para a qual passamos nossa lista de todos os refs (exceto o branch merge-only). Se você tem uma resposta mais elegante para o problema, vamos ouvi-la./*
dosgit for-each-refs
comandos depende de não corresponder a algum arquivo existente e de não terfailglob
ounullglob
definido ( opções do bash , outros shells variam). Você deve citar / escapar os asteriscos ou apenas deixar o rastro/*
(osgit for-each-ref
padrões podem corresponder “do início até uma barra”). Talvez usegrep -Fv refs/remotes/origin/foo
(refs/heads/foo
) para ser mais rígido sobre quais referências são eliminadas.git log --no-merges B1 --not B2
:, onde B1 é o branch no qual você está interessado, e B2 é o branch com o qual você deseja comparar B1. Ambos B1 e B2 podem ser ramos locais ou remotos, portanto, você pode especificargit log --no-merges master --not origin/master
, ou mesmo especificar dois ramos remotos.Isso mostrará todos os commits feitos em seu branch.
fonte
origin/branchName
apontará para o chefe do branch remoto EHEAD
apontará para o commitid do último commit local naquele branch. Portanto, isso não funcionará quando você tiver usado o git push.A resposta @Prakash funciona. Apenas para maior clareza ...
lista os commits no branch-feature, mas não no branch upstream (normalmente seu master).
fonte
Talvez isso possa ajudar:
fonte
Experimente isto:
Basicamente,
git rev-list --all ^branch
obtém todas as revisões que não estão no branch e então você todas as revisões no repositório e subtrai a lista anterior que é as revisões apenas no branch.Após os comentários de @Brian:
Da documentação do git rev-list:
List commits that are reachable by following the parent links from the given commit(s)
Portanto, um comando como
git rev-list A
onde A é um commit listará os commits que podem ser acessados por A inclusive A.Com isso em mente, algo como
git rev-list --all ^A
irá listar commits não alcançáveis de A
Então
git rev-list --all ^branch
irá listar todos os commits não alcançáveis da ponta do branch. O que removerá todos os commits no branch, ou em outras palavras, os commits que estão apenas em outros branches.Agora vamos para
git rev-list --all --not $(git rev-list --all ^branch)
Isso vai ser como
git rev-list --all --not {commits only in other branches}
Então, queremos listar
all
que não são acessíveis a partir deall commits only in other branches
Qual é o conjunto de commits que estão apenas em branch. Vamos dar um exemplo simples:
Aqui, o objetivo é obter D e E, os commits não em qualquer outro branch.
git rev-list --all ^branch
dê apenas BAgora,
git rev-list --all --not B
é aonde chegamos. O que também égit rev-list -all ^B
- queremos todos os commits inacessíveis de B. Em nosso caso, é D e E. O que é o que queremos.Espero que isso explique como o comando funciona corretamente.
Editar após o comentário:
Após os passos acima, se você fizer um
git rev-list --all --not $(git rev-list --all ^merge-only)
, receberá o commit que estava procurando -"bad commit directly on merge-only"
aquele.Mas, depois de realizar a etapa final em suas etapas,
git merge master
o comando não fornecerá a saída esperada. Porque a partir de agora não há commit que não esteja lá apenas no merge, já que o commit extra no master também foi mesclado para merge-only. Portanto,git rev-list --all ^branch
dá um resultado vazio e, portantogit rev-list -all --not $(git rev-list --all ^branch)
, fornecerá todos os commits apenas em fusão.fonte
xargs -L 1 -t git branch -a --contains
mostrar muitos falsos positivos (commits que estão de fato em outros branches). Tentei com e sem--no-merges
. Obrigado por responder!git rev-list --all ^branch
dará a você todos os commits que não estão embranch
. Você está então subtraindo isso da lista que está embranch
; mas, por definição, todos os commits que nãobranch
estão em não estãobranch
, então você não está subtraindo nada. O que Jimmyorr está procurando são commits que estão em,branch
mas não estãomaster
, nem em qualquer outro branch. Você não quer subtrair os commits que não estãobranch
; você deseja subtrair os commits que estão em qualquer outro branch.branch
, mas também funcionagit rev-list branch
. Você está apenas escrevendogit rev-list branch
de uma forma mais complicada (e mais lenta). Não funciona responder a questão, que é como encontrar todos os commitsbranch
que não estão em nenhum outro branch .Outra variação das respostas aceitas, para usar com
master
git log origin/master --not $(git branch -a | grep -Fv master)
Filtre todos os commits que acontecem em qualquer branch diferente do master.
fonte
Esta não é exatamente uma resposta real, mas preciso acessar a formatação e muito espaço. Tentarei descrever a teoria por trás do que considero as duas melhores respostas: a aceita e a (pelo menos atualmente) melhor colocada . Mas, na verdade, eles respondem a perguntas diferentes .
Commits no Git estão frequentemente "em" mais de um branch por vez. Na verdade, é disso que se trata. Dado:
onde as letras maiúsculas representam os hash IDs reais do Git, geralmente procuramos apenas commit
H
ou apenas commitsI-J
em nossagit log
saída. Os commits atéG
estão em ambas as ramificações, portanto, gostaríamos de excluí-los.(Observe que em gráficos desenhados assim, os commits mais recentes estão à direita. Os nomes selecionam o único commit mais à direita nessa linha. Cada um desses commits tem um commit pai, que é o commit à sua esquerda: o pai de
H
éG
, e o pai deJ
éI
. O pai deI
éG
novamente. O pai deG
éF
eF
tem um pai que simplesmente não é mostrado aqui: faz parte da...
seção.)Para este caso particularmente simples, podemos usar:
para ver
I-J
, ou:para ver
H
apenas. O nome do lado direito, após os dois pontos, diz ao Git: sim, esses commits . O nome do lado esquerdo, antes dos dois pontos, diz ao Git: não, não esses commits . Git começa no final - em commitH
ou commitJ
- e funciona de trás para frente . Para (muito) mais sobre isso, consulte Think Like (a) Git .Da forma como a pergunta original é formulada, o desejo é encontrar commits que sejam acessíveis a partir de um nome em particular, mas não de qualquer outro nome na mesma categoria geral. Ou seja, se tivermos um gráfico mais complexo:
poderíamos escolher um desses nomes, como
name4
ouname3
, e perguntar: quais commits podem ser encontrados por esse nome, mas não por qualquer um dos outros nomes? Se escolhermos,name3
a resposta é cometerL
. Se escolhermosname4
, a resposta é nenhum commit: o commit quename4
names é commitN
mas commitN
pode ser encontrado começando emname5
e trabalhando para trás.A resposta aceita funciona com nomes de rastreamento remoto, em vez de nomes de ramificações, e permite que você designe um - aquele escrito
origin/merge-only
- como o nome selecionado e observe todos os outros nomes naquele namespace. Também evita a exibição de mesclagens: se escolhermosname1
como o "nome interessante" e dissermos mostre os commits que podem ser acessados,name1
mas não com qualquer outro nome , veremos o commit de mesclagemM
assim como o commit regularI
.A resposta mais popular é bem diferente. É tudo sobre percorrer o gráfico de commit sem seguir as duas pernas de uma mesclagem e sem mostrar nenhum dos commits que são mesclagens. Se começarmos com
name1
, por exemplo, não mostraremosM
(é um merge), mas assumindo que o primeiro pai de mergeM
é commitI
, nem mesmo olharemos para commitsJ
eK
. Vamos acabar mostrando cometerI
, e também se comprometeH
,G
,F
, e assim por diante-nenhuma delas é commits mesclagem e todos são acessíveis pela partir deM
e trabalhando para trás, visitando apenas o primeiro pai de cada merge cometer.A resposta mais popular é bastante adequada, por exemplo, para ver
master
quandomaster
se destina a ser um branch somente de mesclagem. Se todo o "trabalho real" foi feito nas ramificações laterais que foram posteriormente mescladasmaster
, teremos um padrão como este:onde todos os
o
commits un-letter-named são commits comuns (não-merge) eM
eN
são commits de mesclagem. CommitI
é o commit inicial: o primeiro commit já feito, e o único que deveria estar no master que não é um merge commit. Se ogit log --first-parent --no-merges master
mostrar qualquer commit diferente deI
, temos uma situação como esta:onde queremos ver o commit
*
feito diretamentemaster
, não por meio da fusão de algum branch de recurso.Resumindo, a resposta popular é ótima para saber
master
quandomaster
deve ser somente mesclagem, mas não é tão boa para outras situações. A resposta aceita funciona para essas outras situações.Os nomes de rastreamento remoto são como nomes de
origin/master
agências ?Algumas partes do Git dizem que não são:
diz
on branch master
, mas:diz
HEAD detached at origin/master
. Prefiro concordar comgit checkout
/git switch
:origin/master
não é um nome de branch porque você não pode "ativá-lo".A resposta aceita usa nomes de rastreamento remoto
origin/*
como "nomes de agências":A linha do meio, que invoca
git for-each-ref
, itera sobre os nomes de rastreamento remoto para o remoto nomeadoorigin
.O motivo pelo qual esta é uma boa solução para o problema original é que estamos interessados aqui nos nomes de branch de outra pessoa , e não nos nomes de nosso branch. Mas isso significa que definimos branch como algo diferente de nossos nomes de branch . Tudo bem: apenas esteja ciente de que você está fazendo isso, quando fizer isso.
git log
atravessa alguma (s) parte (s) do gráfico de confirmaçãoO que estamos realmente procurando aqui são séries do que chamei de daglets: veja o que exatamente queremos dizer com "galho"? Ou seja, estamos procurando fragmentos em algum subconjunto do gráfico de confirmação geral .
Sempre que fazemos o Git olhar para um nome de branch como
master
, um nome de tag comov2.1
, ou um nome de rastreamento remoto comoorigin/master
, tendemos a querer que o Git nos diga sobre esse commit e cada commit que podemos obter a partir desse commit: começando aí e trabalhando para trás.Em matemática, isso é conhecido como gráfico de caminhada . O gráfico de commit do Git é um Directed Acyclic Graph ou DAG , e esse tipo de gráfico é particularmente adequado para caminhadas. Ao percorrer esse gráfico, visitaremos cada vértice do gráfico que pode ser alcançado através do caminho que está sendo usado. Os vértices no gráfico Git são os commits, com as arestas sendo arcos - links unilaterais - indo de cada filho para cada pai. (É aqui que entra o Think Like (a) Git . A natureza unilateral dos arcos significa que o Git deve trabalhar de trás para frente, do filho para o pai.)
Os dois comandos principais do Git para caminhar no gráfico são
git log
egit rev-list
. Esses comandos são extremamente semelhantes - na verdade, eles são construídos principalmente a partir dos mesmos arquivos de origem - mas sua saída é diferente:git log
produz saída para humanos lerem, enquantogit rev-list
produz saída para outros programas Git lerem. 1 Ambos os comandos fazem esse tipo de caminhada no gráfico.A caminhada no gráfico que eles fazem é especificamente: dado algum conjunto de commits de ponto de partida (talvez apenas um commit, talvez um monte de IDs de hash, talvez um monte de nomes que resolvem em IDs de hash), ande no gráfico, visitando commits . Diretivas específicas, como
--not
ou um prefixo^
, ou--ancestry-path
, ou--first-parent
, modificam a caminhada do gráfico de alguma forma.Enquanto fazem a caminhada do gráfico, eles visitam cada commit. Mas eles imprimem apenas alguns subconjuntos selecionados dos commits percorridos. Diretivas como
--no-merges
ou--before <date>
informam o código do gráfico que se compromete a imprimir .Para fazer esta visita, um commit de cada vez, esses dois comandos usam uma fila prioritária . Você executa
git log
ougit rev-list
e dá a ele alguns commits de ponto de partida. Eles colocam esses commits na fila de prioridade. Por exemplo, um simples:transforma o nome
master
em um ID de hash bruto e coloca esse ID de hash na fila. Ou:transforma ambos os nomes em IDs de hash e - supondo que sejam dois IDs de hash diferentes - coloca ambos na fila.
A prioridade dos commits nesta fila é determinada por ainda mais argumentos. Por exemplo, o argumento
--author-date-order
informagit log
ougit rev-list
para usar o carimbo de data / hora do autor , em vez do carimbo de data / hora do committer. O padrão é usar o carimbo de data / hora do committer e escolher o commit mais recente por data: aquele com a data numérica mais alta. Assimmaster develop
, assumindo que estes resolvam dois commits diferentes, o Git mostrará o que vier depois primeiro, porque estará na frente da fila.Em qualquer caso, o código de passeio de revisão agora executa em um loop:
--no-merges
: print nothing se for um commit de mesclagem;--before
: não imprime nada se sua data não for anterior à hora designada. Se a impressão não for suprimida, imprima o commit: forgit log
, mostre seu log; paragit rev-list
imprimir seu ID de hash.--first-parent
suprime tudo, exceto o primeiro pai de cada mesclagem.(Ambos
git log
egit rev-list
podem fazer a simplificação do histórico com ou sem a reescrita dos pais neste ponto também, mas vamos pular isso aqui).Para uma cadeia simples, como começar em
HEAD
e trabalhar para trás quando não há commits de mesclagem, a fila sempre tem um commit no topo do loop. Há um commit, então nós o retiramos e imprimimos e colocamos seu (único) pai na fila e contornamos novamente, e seguimos a cadeia de trás para frente até chegarmos ao primeiro commit, ou o usuário se cansa dagit log
saída e sai o programa. Nesse caso, nenhuma das opções de pedido importa: há apenas um commit para mostrar.Quando há fusões e nós seguimos ambos os pais - ambas as "pernas" da fusão - ou quando você dá
git log
ougit rev-list
mais de um commit inicial, as opções de classificação são importantes.Por último, considere o efeito de
--not
ou^
na frente de um especificador de confirmação. Eles têm várias maneiras de escrevê-los:ou:
ou:
todos significam a mesma coisa. O
--not
é como o prefixo,^
exceto que se aplica a mais de um nome:significa não branch1, não branch2, sim branch3; mas:
significa não branch1, não branch2, não branch3, e você tem que usar um segundo
--not
para desligá-lo:o que é um pouco estranho. As duas diretivas "não" são combinadas via XOR, então, se você realmente quiser, pode escrever:
para significar não branch1, não branch2, sim branch3 , se você quiser ofuscar .
Todos eles funcionam afetando a caminhada do gráfico. Conforme
git log
ougit rev-list
percorre o gráfico, ele se certifica de não colocar na fila de prioridade qualquer confirmação que seja alcançável de qualquer uma das referências negadas . (Na verdade, eles afetam a configuração inicial também: commits negados não podem ir para a fila de prioridade diretamente da linha de comando, entãogit log master ^master
não mostra nada, por exemplo.)Toda a sintaxe extravagante descrita na documentação do gitrevisions faz uso disso, e você pode expor isso com uma simples chamada para
git rev-parse
. Por exemplo:A sintaxe de três pontos significa commits acessíveis do lado esquerdo ou direito, mas excluindo commits alcançáveis de ambos . Neste caso, o próprio
origin/master
commit,,b34789c0b
pode ser alcançado a partir deorigin/pu
(fc307aa37...
), então oorigin/master
hash aparece duas vezes, uma vez com uma negação, mas na verdade o Git atinge a sintaxe de três pontos colocando duas referências positivas - os dois IDs de hash não negados - e um negativo, representado pelo^
prefixo.Simiarly:
A
^@
sintaxe significa todos os pais do commit fornecido , emaster^
ela mesma - o primeiro pai do commit selecionado por branch-namemaster
- é um commit de mesclagem, então tem dois pais. Estes são os dois pais. E:O
^!
sufixo significa o próprio compromisso, mas nenhum de seus pais . Nesse caso,master^
é0b07eecf6...
. Já vimos ambos os pais com o^@
sufixo; aqui estão eles de novo, mas desta vez, negados.1 Muitos programas Git literalmente rodam
git rev-list
com várias opções, e lêem sua saída, para saber quais commits e / ou outros objetos Git usar.2 Como o gráfico é acíclico , é possível garantir que nenhum já foi visitado, se adicionarmos a restrição nunca mostrar um pai antes de mostrar todos os seus filhos à prioridade.
--date-order
,--author-date-order
e--topo-order
adicione esta restrição. A ordem de classificação padrão - que não tem nome - não tem. Se os carimbos de data / hora de commits são complicados - se, por exemplo, alguns commits foram feitos "no futuro" por um computador cujo relógio estava desligado - isso poderia em alguns casos levar a uma saída de aparência estranha.Se você chegou até aqui, agora sabe muito sobre
git log
Resumo:
git log
é sobre mostrar alguns commits selecionados enquanto percorre parte ou toda parte do gráfico.--no-merges
argumento, encontrado em ambas as respostas aceitas e atualmente no topo da classificação, suprime a exibição de alguns commits que são percorridos.--first-parent
argumento, da resposta atualmente no topo da classificação, suprime a caminhada de algumas partes do gráfico, durante a própria caminhada do gráfico.--not
prefixo para os argumentos da linha de comando, conforme usado na resposta aceita, elimina a visita a algumas partes do gráfico, desde o início.Obtemos as respostas que gostamos, para duas perguntas diferentes, usando esses recursos.
fonte